├── .gitignore ├── Dockerfile ├── Dockerrun.aws.json ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── scripts └── deploy.sh └── src ├── App.js ├── client ├── allMetrics │ ├── BytesMetrics.jsx │ ├── CpuMetrics.jsx │ ├── NetworkMetrics.jsx │ ├── RamMetrics.jsx │ ├── consumerConvMetrics.jsx │ ├── consumerFailReqMetrics.jsx │ ├── consumerReqMetrics.jsx │ ├── producerConvMetrics.jsx │ ├── producerMessMetrics.jsx │ └── producerReqMetrics.jsx ├── assets │ ├── Cluster.gif │ ├── Kirill.jpeg │ ├── Login-background.jpg │ ├── Logo.png │ ├── More-Details.gif │ ├── Sid-pic.jpeg │ ├── Slava.jpg │ ├── Space.jpg │ ├── Transparent-Logo.png │ ├── chartjs-logo.jpg │ ├── dog.jpg │ ├── express-logo.jpg │ ├── mongodb-logo.jpg │ ├── mui-logo.jpg │ ├── prometheus-logo.png │ ├── react-logo.png │ ├── topics.gif │ ├── transparent-orbital.png │ └── vic-pic.jpeg ├── components │ ├── ClusterDynamicDetails.jsx │ ├── ClusterOverview.jsx │ ├── CreateAlertModal.jsx │ ├── Doc.jsx │ ├── DynamicHealth.jsx │ ├── DynamicOverview.jsx │ ├── DynamicTopics.jsx │ ├── EditCluster.jsx │ ├── HomePageSideBar.jsx │ ├── LandingPageDemo.jsx │ ├── LandingPageHero.jsx │ ├── LandingPageTeam.jsx │ ├── LandingPageTechStack.jsx │ ├── LogoutButton.jsx │ ├── Navbar.jsx │ └── TopicDetail.jsx └── pages │ ├── ClusterDetail.jsx │ ├── Documentation.jsx │ ├── Home.jsx │ ├── LandingPage.jsx │ ├── Login.jsx │ └── Signup.jsx ├── index.css ├── index.js └── server ├── controllers ├── alertController.js ├── clusterController.js ├── kafkaController.js ├── metricsController.js └── userController.js ├── models ├── alertModel.js ├── clusterModel.js └── userModel.js ├── routes ├── alertRouter.js ├── clusterRouter.js ├── kafkaRouter.js ├── metricsRouter.js └── userRouter.js └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Base image 2 | FROM node:lts 3 | 4 | # Create app directory 5 | WORKDIR /orbitals 6 | 7 | # Copy package.json and package-lock.json 8 | COPY . . 9 | 10 | # Install dependencies 11 | RUN npm install 12 | RUN npm run build 13 | # Copy server files and the React app's build folder 14 | 15 | 16 | # Set the NODE_OPTIONS environment variable 17 | ENV NODE_OPTIONS="--max_old_space_size=4096" 18 | 19 | # Expose the port 20 | EXPOSE 3001 21 | 22 | # Start the app 23 | CMD ["npm", "run", "server"] -------------------------------------------------------------------------------- /Dockerrun.aws.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSEBDockerrunVersion": "1", 3 | "Image": { 4 | "Name": "public.ecr.aws/z2f9g1i6/cicd:", 5 | "Update": "true" 6 | }, 7 | "Ports": [ 8 | { 9 | "ContainerPort": "3001" 10 | } 11 | ], 12 | "Volumes": [] 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Orbital 2 | ### Orbital is an open-source tool for visualizing and monitoring Kafka clusters. It is designed to help developers save time and improve efficiency by providing a real-time view of their Kafka clusters. 3 | 4 | 5 | 6 | 7 | ## How Orbital Can Help You 8 | 9 | Orbital is designed to help developers save time and improve efficiency by providing a real-time view of their Kafka clusters. With Orbital, you can: 10 | - Identify potential issues before they become major problems 11 | - Monitor key metrics to ensure optimal performance 12 | - Customize your dashboards to display the information that matters to your org 13 | - Receive alerts and notifications when issues arise 14 | - Provides relevant and useful graphs that update in real-time -- with no additional configuration required 15 | - Presents topics and partitions in a user-friendly visualization for making swift optimization decisions related to consumer performance 16 | - Removes the requirement of manually creating PromQL queries and using unfamiliar CLI commands 17 | 18 | 19 | ## Getting Started 20 | 21 | To use this application, follow these instructions: 22 | 23 | 1. Head over to orbital-view.com 24 | 2. Create an account 25 | 3. Enter your public Prometheus port 26 | 4. Have fun with Orbital! 27 | 28 | ## Features 29 | 30 | Orbital enables you to view your cluster information in the following ways using an interactive GUI: 31 | 32 | **1) View Cluster Health Metrics (with Prometheus Connection)** 33 | 34 | Users can view the overall health of their cluster at a glance on the 'Health Metrics' page which includes auto-updating graphs displaying real-time data readings for CPU Usage, RAM usage, Bytes In and Out, and Network Latency. 35 | 36 | ![cluster](src/client/assets/Cluster.gif) 37 | 38 | **2) View Key Insights into Cluster Producer & Consumers (with Prometheus connection)** 39 | 40 | The 'More Details' page allows users to gain key insights into the Producers and Consumers of their Kafka Cluster, including the number of messages produced and consumed, any failed requests, and conversion data. This allows developers to quickly make optimization decisions about producer and consumer assignments. 41 | 42 | ![more-details](src/client/assets/More-Details.gif) 43 | 44 | 45 | **3) View Data at Topic-Level for Top 5 Most Used Topics (with Prometheus Connection)** 46 | 47 | The 'Topic Metrics' page displays metrics for the most active Cluster topics. Developers can view their top 5 cluster topics, their partitions, and consumer offsets. Click on each topic section to expand and view graphs displaying throughput information at the topic level and click again to collapse. 48 | 49 | ![topics](src/client/assets/topics.gif) 50 | 51 | 52 | 53 | ## Built With 54 | - [React](https://reactjs.org/) - Frontend Framework 55 | - [Material UI](https://mui.com/) - Design Framework 56 | - [ChartJS](https://www.chartjs.org/) - Rendering of metric graphs 57 | - [Node.js](https://nodejs.org/en/) - File system, testing, core application functionality 58 | - [Express](https://expressjs.com/) - Backend Framework 59 | - [MongoDB](https://www.mongodb.com/) - Non-relational database for user accounts and cluster info 60 | - [KafkaJS](https://kafka.js.org/) - Kafka client for Node.js 61 | - [Prometheus](https://prometheus.io/) - Time series database with event monitoring 62 | - [Docker](https://www.docker.com/) - Containerization 63 | - [AWS](https://aws.amazon.com/) - Cloud-computing services 64 | 65 | ## Contributors 66 | 67 | [Victor Gulyak](https://github.com/vicg932) - [LinkedIn](https://www.linkedin.com/in/vic-gul/) 68 |
69 | [Sidney Brodsky](https://github.com/SidneyJB) - [LinkedIn](https://www.linkedin.com/in/sidney-brodsky/) 70 |
71 | [Kirill Karbutov](https://github.com/Karbutov00) - [LinkedIn](https://www.linkedin.com/in/kirill-karbutov/) 72 |
73 | [Slava Melikov](https://github.com/Slavamelikov05) - [LinkedIn](https://www.linkedin.com/in/slava-melikov/) 74 |
75 | 76 | 77 | If you'd like to support the active development of Orbital: 78 | 79 | - Star this repo! 80 | - Write a review or tutorial on Mediumor a personal blog 81 | - Contribute to this project by raising a new issue or making a PR to solve an issue 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orbital", 3 | "version": "0.1.0", 4 | "proxy": "http://localhost:3001", 5 | "private": true, 6 | "dependencies": { 7 | "@emotion/hash": "^0.9.0", 8 | "@emotion/react": "^11.10.6", 9 | "@emotion/styled": "^11.10.6", 10 | "@mui/icons-material": "^5.11.11", 11 | "@mui/material": "^5.11.13", 12 | "@mui/styled-engine-sc": "^5.11.11", 13 | "@testing-library/jest-dom": "^5.16.5", 14 | "@testing-library/react": "^13.4.0", 15 | "@testing-library/user-event": "^13.5.0", 16 | "axios": "^1.3.5", 17 | "bcrypt": "^5.1.0", 18 | "chart.js": "^4.2.1", 19 | "cloudinary": "^1.35.0", 20 | "concurrently": "^7.6.0", 21 | "cors": "^2.8.5", 22 | "deep-email-validator": "^0.1.21", 23 | "dotenv": "^16.0.3", 24 | "express": "^4.18.2", 25 | "image-downloader": "^4.3.0", 26 | "jsonwebtoken": "^9.0.0", 27 | "kafkajs": "^2.2.4", 28 | "mongodb": "^5.1.0", 29 | "mongoose": "^7.0.2", 30 | "nodemon": "^2.0.21", 31 | "openai": "^3.2.1", 32 | "react": "^18.2.0", 33 | "react-chartjs-2": "^5.2.0", 34 | "react-dom": "^18.2.0", 35 | "react-router-dom": "^6.9.0", 36 | "react-scripts": "5.0.1", 37 | "styled-components": "^5.3.9", 38 | "web-vitals": "^2.1.4" 39 | }, 40 | "scripts": { 41 | "start": "concurrently \"npm run server\" \"npm run client\"", 42 | "server": "nodemon src/server/server.js", 43 | "client": "react-scripts start", 44 | "build": "react-scripts build", 45 | "test": "react-scripts test", 46 | "eject": "react-scripts eject" 47 | }, 48 | "eslintConfig": { 49 | "extends": [ 50 | "react-app", 51 | "react-app/jest" 52 | ] 53 | }, 54 | "browserslist": { 55 | "production": [ 56 | ">0.2%", 57 | "not dead", 58 | "not op_mini all" 59 | ], 60 | "development": [ 61 | "last 1 chrome version", 62 | "last 1 firefox version", 63 | "last 1 safari version" 64 | ] 65 | }, 66 | "devDependencies": { 67 | "faker": "^6.6.6" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | Orbital 17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | echo "Processing deploy.sh" 2 | # Set EB BUCKET as env variable 3 | EB_BUCKET=elasticbeanstalk-us-east-1-341266217751 4 | # Set the default region for aws cli 5 | aws configure set default.region us-east-1 6 | # Log in to ECR 7 | eval $(aws ecr get-login --no-include-email --region us-east-1) 8 | # Build docker image based on our production Dockerfile 9 | docker build -t cicd . 10 | # tag the image with the GitHub SHA 11 | docker tag cicd:latest public.ecr.aws/z2f9g1i6/cicd:$GITHUB_SHA 12 | # Push built image to ECS 13 | docker push public.ecr.aws/z2f9g1i6/cicd:$GITHUB_SHA 14 | # Use the linux sed command to replace the text '' in our Dockerrun file with the GitHub SHA key 15 | sed -i='' "s//$GITHUB_SHA/" Dockerrun.aws.json 16 | # Zip up our codebase, along with modified Dockerrun and our .ebextensions directory 17 | zip -r cicd1.zip Dockerrun.aws.json 18 | # Upload zip file to s3 bucket 19 | aws s3 cp cicd1.zip s3://$EB_BUCKET/cicd1.zip 20 | # Create a new application version with new Dockerrun 21 | aws elasticbeanstalk create-application-version --application-name Testing-App --version-label $GITHUB_SHA --source-bundle S3Bucket=$EB_BUCKET,S3Key=cicd1.zip 22 | # Update environment to use new version number 23 | aws elasticbeanstalk update-environment --environment-name Testing-App-env --version-label $GITHUB_SHA -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | // 2 | import { Routes, Route } from "react-router-dom"; 3 | import Login from "./client/pages/Login"; 4 | import Signup from "./client/pages/Signup"; 5 | import Home from "./client/pages/Home"; 6 | import LandingPage from "./client/pages/LandingPage"; 7 | import Documentation from "./client/pages/Documentation"; 8 | import ClusterDetail from "./client/pages/ClusterDetail"; 9 | import { useState } from "react"; 10 | 11 | function App() { 12 | const [user, setUser] = useState({}); 13 | return ( 14 |
15 | 16 | } 19 | /> 20 | } /> 21 | } 24 | /> 25 | } /> 26 | } /> 27 | } /> 28 | 29 |
30 | ); 31 | } 32 | 33 | export default App; 34 | -------------------------------------------------------------------------------- /src/client/allMetrics/BytesMetrics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | import { Card, CardHeader } from '@mui/material'; 4 | 5 | 6 | 7 | const BytesMetrics = ({ bytesOutMetrics, bytesInMetrics }) => { 8 | const chartRef = useRef(null); 9 | const [labels, setLabels] = useState([ 10 | '-15s', 11 | '', 12 | '', 13 | '', 14 | '', 15 | '', 16 | '', 17 | '', 18 | '', 19 | '', 20 | '', 21 | '', 22 | '', 23 | '', 24 | 'Now', 25 | ]); 26 | 27 | // Declares initial state of zero for Bytes In and Bytes Out charts 28 | const [bytesInData, setBytesInData] = useState([ 29 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30 | ]); 31 | const [bytesOutData, setBytesOutData] = useState([ 32 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33 | ]); 34 | 35 | 36 | // Updates Bytes In and Bytes Out chart data every second 37 | useEffect(() => { 38 | const interval = setInterval(() => { 39 | const newBytesInValue = bytesInMetrics; 40 | const newBytesOutValue = bytesOutMetrics; 41 | setBytesInData([...bytesInData.slice(1), newBytesInValue]); 42 | setBytesOutData([...bytesOutData.slice(1), newBytesOutValue]); 43 | }, 1000); 44 | 45 | return () => clearInterval(interval); 46 | }); 47 | 48 | // Renders charts for Bytes In and Bytes Out with appropriate axis labels and scales 49 | useEffect(() => { 50 | const chartCtx = chartRef.current.getContext('2d'); 51 | 52 | // Creates a chart instance 53 | const chart = new Chart(chartCtx, { 54 | type: 'line', 55 | data: { 56 | labels: labels, 57 | datasets: [ 58 | { 59 | label: 'Bytes In', 60 | data: bytesInData, 61 | borderColor: '#9695ff', 62 | backgroundColor: 'rgba(150, 149, 255, 0.2)', 63 | pointBackgroundColor: '#ffffff', 64 | pointBorderColor: '#484995', 65 | borderWidth: 3, 66 | }, 67 | { 68 | label: 'Bytes Out', 69 | data: bytesOutData, 70 | borderColor: '#6968d4', 71 | backgroundColor: 'rgba(105, 104, 212, 0.2)', 72 | pointBackgroundColor: '#ffffff', 73 | pointBorderColor: '#1f2044', 74 | borderWidth: 3, 75 | }, 76 | ], 77 | }, 78 | options: { 79 | scales: { 80 | y: { 81 | max: 5000, 82 | min: 0, 83 | ticks: { 84 | stepSize: 150, 85 | beginAtZero: true, 86 | }, 87 | title: { 88 | display: true, 89 | text: 'Bytes', 90 | font: { 91 | size: 14, 92 | }, 93 | }, 94 | }, 95 | x: { 96 | title: { 97 | display: true, 98 | text: 'Time', 99 | font: { 100 | size: 14, 101 | }, 102 | }, 103 | }, 104 | }, 105 | animation: false, 106 | plugins: { 107 | legend: { 108 | display: true, 109 | labels: { 110 | font: { 111 | size: 14, 112 | }, 113 | }, 114 | }, 115 | tooltip: { 116 | titleFont: { 117 | size: 16, 118 | }, 119 | bodyFont: { 120 | size: 14, 121 | }, 122 | }, 123 | }, 124 | backgroundColor: 'rgba(105, 104, 212, 0.2)', 125 | }, 126 | title: { 127 | display: true, 128 | text: (ctx) => 'Point Style: ' + ctx.chart.data.datasets[0].pointStyle, 129 | }, 130 | }); 131 | 132 | // Destroys previous chart instance before creating a new one with updated state 133 | return () => { 134 | chart.destroy(); 135 | }; 136 | }, [bytesInData, bytesOutData]); 137 | 138 | return ( 139 | 140 | 141 | 142 | 143 | ); 144 | }; 145 | 146 | export default BytesMetrics; -------------------------------------------------------------------------------- /src/client/allMetrics/CpuMetrics.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | BarElement, 7 | Title, 8 | Tooltip, 9 | Legend, 10 | } from "chart.js"; 11 | import { Bar } from "react-chartjs-2"; 12 | import { Card, CardHeader } from "@mui/material"; 13 | 14 | // Creates a chart instance for CPU metrics displaying a percentage for CPU usage 15 | const CpuMetrics = ({ cpuMetrics }) => { 16 | const cpuMetric = cpuMetrics * 100; 17 | 18 | ChartJS.register( 19 | CategoryScale, 20 | LinearScale, 21 | BarElement, 22 | Title, 23 | Tooltip, 24 | Legend 25 | ); 26 | 27 | const options = { 28 | responsive: true, 29 | plugins: { 30 | legend: { 31 | position: "top", 32 | }, 33 | }, 34 | scales: { 35 | y: { 36 | max: 100, 37 | ticks: { 38 | stepSize: 10, 39 | beginAtZero: true, 40 | }, 41 | title: { 42 | display: true, 43 | text: "Percentage", 44 | font: { 45 | size: 14, 46 | }, 47 | }, 48 | }, 49 | }, 50 | }; 51 | 52 | const data = { 53 | labels: [""], 54 | datasets: [ 55 | { 56 | label: "Current CPU", 57 | data: [cpuMetric], 58 | backgroundColor: "rgba(150, 149, 255, 0.5)", 59 | }, 60 | ], 61 | }; 62 | 63 | return ( 64 | 65 | 66 | 71 | 72 | ); 73 | }; 74 | 75 | export default CpuMetrics; -------------------------------------------------------------------------------- /src/client/allMetrics/NetworkMetrics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | import { Card, CardHeader } from '@mui/material'; 4 | 5 | const NetworkMetrics = ({ latency }) => { 6 | const chartRef = useRef(null); 7 | const [labels, setLabels] = useState([ 8 | '-15s', 9 | '', 10 | '', 11 | '', 12 | '', 13 | '', 14 | '', 15 | '', 16 | '', 17 | '', 18 | '', 19 | '', 20 | '', 21 | '', 22 | 'Now', 23 | ]); 24 | 25 | // Declares initial state of zero for Network Latency metric chart 26 | const [bytesInData, setBytesInData] = useState([ 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | ]); 29 | const [bytesOutData, setBytesOutData] = useState([ 30 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31 | ]); 32 | 33 | // Updates Network latency chart data every second 34 | useEffect(() => { 35 | const interval = setInterval(() => { 36 | const newBytesInValue = latency; 37 | setBytesInData([...bytesInData.slice(1), newBytesInValue]); 38 | }, 1000); 39 | 40 | return () => clearInterval(interval); 41 | }); 42 | 43 | useEffect(() => { 44 | const chartCtx = chartRef.current.getContext('2d'); 45 | 46 | // Creates a chart instance 47 | const chart = new Chart(chartCtx, { 48 | type: 'line', 49 | data: { 50 | labels: labels, 51 | datasets: [ 52 | { 53 | label: 'Latency', 54 | data: bytesInData, 55 | borderColor: 'rgba(255, 206, 86, 1)', 56 | backgroundColor: 'rgba(255, 206, 86, 0.2)', 57 | pointBackgroundColor: 'rgba(255, 206, 86, 1)', 58 | pointBorderColor: 'rgba(255, 206, 86, 1)', 59 | borderWidth: 3, 60 | }, 61 | ], 62 | }, 63 | options: { 64 | scales: { 65 | y: { 66 | max: 2000, 67 | min: 0, 68 | ticks: { 69 | stepSize: 50, 70 | beginAtZero: true, 71 | }, 72 | title: { 73 | display: true, 74 | text: 'Milliseconds', 75 | font: { 76 | size: 14, 77 | }, 78 | }, 79 | }, 80 | x: { 81 | title: { 82 | display: true, 83 | text: 'Time', 84 | font: { 85 | size: 14, 86 | }, 87 | }, 88 | }, 89 | }, 90 | animation: false, 91 | }, 92 | title: { 93 | display: true, 94 | text: (ctx) => 'Point Style: ' + ctx.chart.data.datasets[0].pointStyle, 95 | }, 96 | }); 97 | 98 | // Destroy previous chart instance before creating a new one with updated state 99 | return () => { 100 | chart.destroy(); 101 | }; 102 | }, [bytesInData, bytesOutData]); 103 | 104 | return ( 105 | 106 | 107 | 108 | 109 | ); 110 | }; 111 | 112 | export default NetworkMetrics; -------------------------------------------------------------------------------- /src/client/allMetrics/RamMetrics.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | BarElement, 7 | Title, 8 | Tooltip, 9 | Legend, 10 | } from 'chart.js'; 11 | import { Card, CardHeader } from '@mui/material'; 12 | import { Bar } from 'react-chartjs-2'; 13 | 14 | // Creates a chart instance for Ram metrics displaying a percentage for RAM usage 15 | const RamMetrics = ({ ramUsage }) => { 16 | const ramUsageMetric = ramUsage / 1000 || 0; 17 | 18 | ChartJS.register( 19 | CategoryScale, 20 | LinearScale, 21 | BarElement, 22 | Title, 23 | Tooltip, 24 | Legend 25 | ); 26 | 27 | const options = { 28 | responsive: true, 29 | plugins: { 30 | legend: { 31 | position: 'top', 32 | }, 33 | }, 34 | scales: { 35 | y: { 36 | max: 2000, 37 | ticks: { 38 | stepSize: 300, 39 | beginAtZero: true, 40 | }, 41 | title: { 42 | display: true, 43 | text: 'Megabytes', 44 | font: { 45 | size: 14, 46 | }, 47 | }, 48 | }, 49 | }, 50 | }; 51 | 52 | const data = { 53 | labels: [''], 54 | datasets: [ 55 | { 56 | label: 'Current RAM Usage', 57 | data: [ramUsageMetric], 58 | backgroundColor: 'rgba(150, 149, 255, 0.5)', 59 | }, 60 | ], 61 | }; 62 | 63 | return ( 64 | 65 | 66 | 71 | 72 | ); 73 | }; 74 | 75 | export default RamMetrics; -------------------------------------------------------------------------------- /src/client/allMetrics/consumerConvMetrics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | import { Card, CardHeader } from '@mui/material'; 4 | 5 | const ConsumerConvMetrics = ({ conConvMetrics }) => { 6 | const chartRef = useRef(null); 7 | const [labels, setLabels] = useState([ 8 | '-15s', 9 | '', 10 | '', 11 | '', 12 | '', 13 | '', 14 | '', 15 | '', 16 | '', 17 | '', 18 | '', 19 | '', 20 | '', 21 | '', 22 | 'Now', 23 | ]); 24 | 25 | // Declares initial state of zero for Consumer Conversion metric charts 26 | const [bytesInData, setBytesInData] = useState([ 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | ]); 29 | const [bytesOutData, setBytesOutData] = useState([ 30 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31 | ]); 32 | 33 | // Updates Consumer Conversion chart data every second 34 | useEffect(() => { 35 | const interval = setInterval(() => { 36 | const newBytesInValue = conConvMetrics; 37 | setBytesInData([...bytesInData.slice(1), newBytesInValue]); 38 | }, 1000); 39 | 40 | return () => clearInterval(interval); 41 | }); 42 | 43 | // Renders charts for Consumer Conversion metric with appropriate axis labels and scales 44 | useEffect(() => { 45 | const chartCtx = chartRef.current.getContext('2d'); 46 | 47 | // Creates a chart instance 48 | const chart = new Chart(chartCtx, { 49 | type: 'line', 50 | data: { 51 | labels: labels, 52 | datasets: [ 53 | { 54 | label: 'Consumer Conversions', 55 | data: bytesInData, 56 | borderColor: 'rgba(75, 192, 192, 1)', 57 | backgroundColor: 'rgba(75, 192, 192, 0.2)', 58 | pointBackgroundColor: 'rgba(75, 192, 192, 1)', 59 | pointBorderColor: 'rgba(75, 192, 192, 1)', 60 | borderWidth: 3, 61 | }, 62 | ], 63 | }, 64 | options: { 65 | scales: { 66 | y: { 67 | max: (function () { 68 | const maxValue = Math.max(...bytesInData); 69 | if (maxValue < 5) return 10; 70 | if (maxValue >= 10 && maxValue < 1000) return maxValue + 10; 71 | })(), 72 | min: 0, 73 | ticks: { 74 | stepSize: 5, 75 | beginAtZero: true, 76 | }, 77 | title: { 78 | display: true, 79 | text: 'Conversions', 80 | font: { 81 | size: 14, 82 | }, 83 | }, 84 | }, 85 | x: { 86 | title: { 87 | display: true, 88 | text: 'Time', 89 | font: { 90 | size: 14, 91 | }, 92 | }, 93 | }, 94 | }, 95 | animation: false, 96 | }, 97 | title: { 98 | display: true, 99 | text: (ctx) => 'Point Style: ' + ctx.chart.data.datasets[0].pointStyle, 100 | }, 101 | }); 102 | 103 | // Destroys previous chart instance before creating a new one with updated state 104 | return () => { 105 | chart.destroy(); 106 | }; 107 | }, [bytesInData, bytesOutData]); 108 | 109 | return ( 110 | 111 | 115 | 116 | 117 | ); 118 | }; 119 | 120 | export default ConsumerConvMetrics; -------------------------------------------------------------------------------- /src/client/allMetrics/consumerFailReqMetrics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | import { Card, CardHeader } from '@mui/material'; 4 | 5 | const ConsumerFailReqMetrics = ({ conFailReqMetrics }) => { 6 | const chartRef = useRef(null); 7 | const [labels, setLabels] = useState([ 8 | '-15s', 9 | '', 10 | '', 11 | '', 12 | '', 13 | '', 14 | '', 15 | '', 16 | '', 17 | '', 18 | '', 19 | '', 20 | '', 21 | '', 22 | 'Now', 23 | ]); 24 | 25 | // Declares initial state of zero for Consumer Failed Request metric chart 26 | const [bytesInData, setBytesInData] = useState([ 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | ]); 29 | const [bytesOutData, setBytesOutData] = useState([ 30 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31 | ]); 32 | 33 | // Updates Consumer Failed Request chart data every second 34 | useEffect(() => { 35 | const interval = setInterval(() => { 36 | const newBytesInValue = conFailReqMetrics; 37 | setBytesInData([...bytesInData.slice(1), newBytesInValue]); 38 | }, 1000); 39 | 40 | return () => clearInterval(interval); 41 | }); 42 | 43 | // Renders charts for Consumer Failed Request metric with appropriate axis labels and scales 44 | useEffect(() => { 45 | const chartCtx = chartRef.current.getContext('2d'); 46 | 47 | // Creates a chart instance 48 | const chart = new Chart(chartCtx, { 49 | type: 'line', 50 | data: { 51 | labels: labels, 52 | datasets: [ 53 | { 54 | label: 'Consumer Failed Requests', 55 | data: bytesInData, 56 | borderColor: 'rgba(75, 192, 192, 1)', 57 | backgroundColor: 'rgba(75, 192, 192, 0.2)', 58 | pointBackgroundColor: 'rgba(75, 192, 192, 1)', 59 | pointBorderColor: 'rgba(75, 192, 192, 1)', 60 | borderWidth: 3, 61 | }, 62 | ], 63 | }, 64 | options: { 65 | scales: { 66 | y: { 67 | max: (function () { 68 | const maxValue = Math.max(...bytesInData); 69 | if (maxValue < 5) return 10; 70 | if (maxValue >= 10 && maxValue < 1000) return maxValue + 10; 71 | })(), 72 | min: 0, 73 | ticks: { 74 | stepSize: 5, 75 | beginAtZero: true, 76 | }, 77 | title: { 78 | display: true, 79 | text: 'Failed Requests', 80 | font: { 81 | size: 14, 82 | }, 83 | }, 84 | }, 85 | x: { 86 | title: { 87 | display: true, 88 | text: 'Time', 89 | font: { 90 | size: 14, 91 | }, 92 | }, 93 | }, 94 | }, 95 | animation: false, 96 | }, 97 | title: { 98 | display: true, 99 | text: (ctx) => 'Point Style: ' + ctx.chart.data.datasets[0].pointStyle, 100 | }, 101 | }); 102 | 103 | // Destroys previous chart instance before creating a new one with updated state 104 | return () => { 105 | chart.destroy(); 106 | }; 107 | }, [bytesInData, bytesOutData]); 108 | 109 | return ( 110 | 111 | 115 | 116 | 117 | ); 118 | }; 119 | 120 | export default ConsumerFailReqMetrics; -------------------------------------------------------------------------------- /src/client/allMetrics/consumerReqMetrics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | import { Card, CardHeader } from '@mui/material'; 4 | 5 | const ConsumerReqMetrics = ({ conReqMetrics }) => { 6 | const chartRef = useRef(null); 7 | const [labels, setLabels] = useState([ 8 | '-15s', 9 | '', 10 | '', 11 | '', 12 | '', 13 | '', 14 | '', 15 | '', 16 | '', 17 | '', 18 | '', 19 | '', 20 | '', 21 | '', 22 | 'Now', 23 | ]); 24 | 25 | // Declares initial state of zero for Consumer Requests per Second metric chart 26 | const [bytesInData, setBytesInData] = useState([ 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | ]); 29 | const [bytesOutData, setBytesOutData] = useState([ 30 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31 | ]); 32 | 33 | // Updates Consumer Requests per Second chart data every second 34 | useEffect(() => { 35 | const interval = setInterval(() => { 36 | const newBytesInValue = conReqMetrics; 37 | setBytesInData([...bytesInData.slice(1), newBytesInValue]); 38 | }, 1000); 39 | 40 | return () => clearInterval(interval); 41 | }); 42 | 43 | // Renders charts for Consumer Requests per Second metric with appropriate axis labels and scales 44 | useEffect(() => { 45 | const chartCtx = chartRef.current.getContext('2d'); 46 | 47 | // Creates a chart instance 48 | const chart = new Chart(chartCtx, { 49 | type: 'line', 50 | data: { 51 | labels: labels, 52 | datasets: [ 53 | { 54 | label: 'Consumer Requests per second', 55 | data: bytesInData, 56 | borderColor: 'rgba(75, 192, 192, 1)', 57 | backgroundColor: 'rgba(75, 192, 192, 0.2)', 58 | pointBackgroundColor: 'rgba(75, 192, 192, 1)', 59 | pointBorderColor: 'rgba(75, 192, 192, 1)', 60 | borderWidth: 3, 61 | }, 62 | ], 63 | }, 64 | options: { 65 | scales: { 66 | y: { 67 | max: (function () { 68 | const maxValue = Math.max(...bytesInData); 69 | if (maxValue < 5) return 10; 70 | if (maxValue >= 10 && maxValue < 1000) return maxValue + 10; 71 | })(), 72 | min: 0, 73 | ticks: { 74 | stepSize: 5, 75 | beginAtZero: true, 76 | }, 77 | title: { 78 | display: true, 79 | text: 'Milliseconds', 80 | font: { 81 | size: 14, 82 | }, 83 | }, 84 | }, 85 | x: { 86 | title: { 87 | display: true, 88 | text: 'Time', 89 | font: { 90 | size: 14, 91 | }, 92 | }, 93 | }, 94 | }, 95 | animation: false, 96 | }, 97 | title: { 98 | display: true, 99 | text: (ctx) => 'Point Style: ' + ctx.chart.data.datasets[0].pointStyle, 100 | }, 101 | }); 102 | 103 | // Destroys previous chart instance before creating a new one 104 | return () => { 105 | chart.destroy(); 106 | }; 107 | }, [bytesInData, bytesOutData]); 108 | 109 | return ( 110 | 111 | 112 | 113 | 114 | ); 115 | }; 116 | 117 | export default ConsumerReqMetrics; -------------------------------------------------------------------------------- /src/client/allMetrics/producerConvMetrics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | import { Card, CardHeader } from '@mui/material'; 4 | 5 | const ProducerConvMetrics = ({ prodConvMetrics }) => { 6 | const chartRef = useRef(null); 7 | const [labels, setLabels] = useState([ 8 | '-15s', 9 | '', 10 | '', 11 | '', 12 | '', 13 | '', 14 | '', 15 | '', 16 | '', 17 | '', 18 | '', 19 | '', 20 | '', 21 | '', 22 | 'Now', 23 | ]); 24 | 25 | // Declares initial state of zero for Producer Conversion metric chart 26 | const [bytesInData, setBytesInData] = useState([ 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | ]); 29 | const [bytesOutData, setBytesOutData] = useState([ 30 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31 | ]); 32 | 33 | // Updates Producer Conversion chart data every second 34 | useEffect(() => { 35 | const interval = setInterval(() => { 36 | const newBytesInValue = prodConvMetrics; 37 | setBytesInData([...bytesInData.slice(1), newBytesInValue]); 38 | }, 1000); 39 | 40 | return () => clearInterval(interval); 41 | }); 42 | 43 | useEffect(() => { 44 | const chartCtx = chartRef.current.getContext('2d'); 45 | 46 | // Creates chart instance 47 | const chart = new Chart(chartCtx, { 48 | type: 'line', 49 | data: { 50 | labels: labels, 51 | datasets: [ 52 | { 53 | label: 'Producer Conversions', 54 | data: bytesInData, 55 | borderColor: 'rgba(255, 206, 86, 1)', 56 | backgroundColor: 'rgba(255, 206, 86, 0.2)', 57 | pointBackgroundColor: 'rgba(255, 206, 86, 1)', 58 | pointBorderColor: 'rgba(255, 206, 86, 1)', 59 | borderWidth: 3, 60 | }, 61 | ], 62 | }, 63 | options: { 64 | scales: { 65 | y: { 66 | max: (function () { 67 | const maxValue = Math.max(...bytesInData); 68 | if (maxValue < 5) return 10; 69 | if (maxValue >= 10 && maxValue < 1000) return maxValue + 10; 70 | })(), 71 | min: 0, 72 | ticks: { 73 | stepSize: 5, 74 | beginAtZero: true, 75 | }, 76 | title: { 77 | display: true, 78 | text: 'Milliseconds', 79 | font: { 80 | size: 14, 81 | }, 82 | }, 83 | }, 84 | x: { 85 | title: { 86 | display: true, 87 | text: 'Time', 88 | font: { 89 | size: 14, 90 | }, 91 | }, 92 | }, 93 | }, 94 | animation: false, 95 | }, 96 | title: { 97 | display: true, 98 | text: (ctx) => 'Point Style: ' + ctx.chart.data.datasets[0].pointStyle, 99 | }, 100 | }); 101 | 102 | // Destroy previous chart instance before creating a new one with updated state 103 | return () => { 104 | chart.destroy(); 105 | }; 106 | }, [bytesInData, bytesOutData]); 107 | 108 | return ( 109 | 110 | 114 | 115 | 116 | ); 117 | }; 118 | 119 | export default ProducerConvMetrics; -------------------------------------------------------------------------------- /src/client/allMetrics/producerMessMetrics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | import { Card, CardHeader } from '@mui/material'; 4 | 5 | const ProducerMessMetrics = ({ prodMessMetrics }) => { 6 | const chartRef = useRef(null); 7 | const [labels, setLabels] = useState([ 8 | '-15s', 9 | '', 10 | '', 11 | '', 12 | '', 13 | '', 14 | '', 15 | '', 16 | '', 17 | '', 18 | '', 19 | '', 20 | '', 21 | '', 22 | 'Now', 23 | ]); 24 | 25 | // Declares initial state of zero for Producer Messages metric chart 26 | const [bytesInData, setBytesInData] = useState([ 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | ]); 29 | const [bytesOutData, setBytesOutData] = useState([ 30 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31 | ]); 32 | 33 | // Updates Producer Messages chart data every second 34 | useEffect(() => { 35 | const interval = setInterval(() => { 36 | const newBytesInValue = prodMessMetrics; 37 | setBytesInData([...bytesInData.slice(1), newBytesInValue]); 38 | }, 1000); 39 | 40 | return () => clearInterval(interval); 41 | }); 42 | 43 | useEffect(() => { 44 | const chartCtx = chartRef.current.getContext('2d'); 45 | 46 | // Creates chart instance 47 | const chart = new Chart(chartCtx, { 48 | type: 'line', 49 | data: { 50 | labels: labels, 51 | datasets: [ 52 | { 53 | label: 'Producer Messages', 54 | data: bytesInData, 55 | borderColor: 'rgba(255, 206, 86, 1)', 56 | backgroundColor: 'rgba(255, 206, 86, 0.2)', 57 | pointBackgroundColor: 'rgba(255, 206, 86, 1)', 58 | pointBorderColor: 'rgba(255, 206, 86, 1)', 59 | borderWidth: 3, 60 | }, 61 | ], 62 | }, 63 | options: { 64 | scales: { 65 | y: { 66 | max: (function () { 67 | const maxValue = Math.max(...bytesInData); 68 | if (maxValue < 5) return 10; 69 | if (maxValue >= 10 && maxValue < 1000) return maxValue + 10; 70 | })(), 71 | min: 0, 72 | ticks: { 73 | stepSize: 5, 74 | beginAtZero: true, 75 | }, 76 | title: { 77 | display: true, 78 | text: 'Milliseconds', 79 | font: { 80 | size: 14, 81 | }, 82 | }, 83 | }, 84 | x: { 85 | title: { 86 | display: true, 87 | text: 'Time', 88 | font: { 89 | size: 14, 90 | }, 91 | }, 92 | }, 93 | }, 94 | animation: false, 95 | }, 96 | title: { 97 | display: true, 98 | text: (ctx) => 'Point Style: ' + ctx.chart.data.datasets[0].pointStyle, 99 | }, 100 | }); 101 | 102 | // Destroys previous chart instance before creating a new one with updated state 103 | return () => { 104 | chart.destroy(); 105 | }; 106 | }, [bytesInData, bytesOutData]); 107 | 108 | return ( 109 | 110 | 111 | 112 | 113 | ); 114 | }; 115 | 116 | export default ProducerMessMetrics; -------------------------------------------------------------------------------- /src/client/allMetrics/producerReqMetrics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | import { Card, CardHeader } from '@mui/material'; 4 | 5 | const ProducerReqMetrics = ({ prodReqMetrics }) => { 6 | const chartRef = useRef(null); 7 | const [labels, setLabels] = useState([ 8 | '-15s', 9 | '', 10 | '', 11 | '', 12 | '', 13 | '', 14 | '', 15 | '', 16 | '', 17 | '', 18 | '', 19 | '', 20 | '', 21 | '', 22 | 'Now', 23 | ]); 24 | 25 | // Declares initial state of zero for Producer Requests per Second metric chart 26 | const [bytesInData, setBytesInData] = useState([ 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | ]); 29 | const [bytesOutData, setBytesOutData] = useState([ 30 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31 | ]); 32 | 33 | // Updates Producer Requests per Second chart data every second 34 | useEffect(() => { 35 | const interval = setInterval(() => { 36 | const newBytesInValue = prodReqMetrics; 37 | setBytesInData([...bytesInData.slice(1), newBytesInValue]); 38 | }, 1000); 39 | 40 | return () => clearInterval(interval); 41 | }); 42 | 43 | useEffect(() => { 44 | const chartCtx = chartRef.current.getContext('2d'); 45 | 46 | // Creates chart instance 47 | const chart = new Chart(chartCtx, { 48 | type: 'line', 49 | data: { 50 | labels: labels, 51 | datasets: [ 52 | { 53 | label: 'Producer Requests per second', 54 | data: bytesInData, 55 | borderColor: 'rgba(255, 206, 86, 1)', 56 | backgroundColor: 'rgba(255, 206, 86, 0.2)', 57 | pointBackgroundColor: 'rgba(255, 206, 86, 1)', 58 | pointBorderColor: 'rgba(255, 206, 86, 1)', 59 | borderWidth: 3, 60 | }, 61 | ], 62 | }, 63 | options: { 64 | scales: { 65 | y: { 66 | max: (function () { 67 | const maxValue = Math.max(...bytesInData); 68 | if (maxValue < 5) return 10; 69 | if (maxValue >= 10 && maxValue < 1000) return maxValue + 10; 70 | })(), 71 | min: 0, 72 | ticks: { 73 | stepSize: 5, 74 | beginAtZero: true, 75 | }, 76 | title: { 77 | display: true, 78 | text: 'Milliseconds', 79 | font: { 80 | size: 14, 81 | }, 82 | }, 83 | }, 84 | x: { 85 | title: { 86 | display: true, 87 | text: 'Time', 88 | font: { 89 | size: 14, 90 | }, 91 | }, 92 | }, 93 | }, 94 | animation: false, 95 | }, 96 | title: { 97 | display: true, 98 | text: (ctx) => 'Point Style: ' + ctx.chart.data.datasets[0].pointStyle, 99 | }, 100 | }); 101 | 102 | // Destroys previous chart instance before creating a new one with updated state 103 | return () => { 104 | chart.destroy(); 105 | }; 106 | }, [bytesInData, bytesOutData]); 107 | 108 | return ( 109 | 110 | 111 | 112 | 113 | ); 114 | }; 115 | 116 | export default ProducerReqMetrics; -------------------------------------------------------------------------------- /src/client/assets/Cluster.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/Cluster.gif -------------------------------------------------------------------------------- /src/client/assets/Kirill.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/Kirill.jpeg -------------------------------------------------------------------------------- /src/client/assets/Login-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/Login-background.jpg -------------------------------------------------------------------------------- /src/client/assets/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/Logo.png -------------------------------------------------------------------------------- /src/client/assets/More-Details.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/More-Details.gif -------------------------------------------------------------------------------- /src/client/assets/Sid-pic.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/Sid-pic.jpeg -------------------------------------------------------------------------------- /src/client/assets/Slava.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/Slava.jpg -------------------------------------------------------------------------------- /src/client/assets/Space.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/Space.jpg -------------------------------------------------------------------------------- /src/client/assets/Transparent-Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/Transparent-Logo.png -------------------------------------------------------------------------------- /src/client/assets/chartjs-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/chartjs-logo.jpg -------------------------------------------------------------------------------- /src/client/assets/dog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/dog.jpg -------------------------------------------------------------------------------- /src/client/assets/express-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/express-logo.jpg -------------------------------------------------------------------------------- /src/client/assets/mongodb-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/mongodb-logo.jpg -------------------------------------------------------------------------------- /src/client/assets/mui-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/mui-logo.jpg -------------------------------------------------------------------------------- /src/client/assets/prometheus-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/prometheus-logo.png -------------------------------------------------------------------------------- /src/client/assets/react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/react-logo.png -------------------------------------------------------------------------------- /src/client/assets/topics.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/topics.gif -------------------------------------------------------------------------------- /src/client/assets/transparent-orbital.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/transparent-orbital.png -------------------------------------------------------------------------------- /src/client/assets/vic-pic.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/orbital/30f6a836bd686d327b4efd952901bc6faba28084/src/client/assets/vic-pic.jpeg -------------------------------------------------------------------------------- /src/client/components/ClusterDynamicDetails.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useEffect, useState } from 'react'; 3 | import axios from 'axios'; 4 | import ClusterOverview from './ClusterOverview'; 5 | import DynamicOverview from './DynamicOverview'; 6 | import DynamicHealth from './DynamicHealth'; 7 | import DynamicTopics from './DynamicTopics'; 8 | import { useParams } from 'react-router-dom'; 9 | 10 | const ClusterDynamicDetails = ({ currentTab }) => { 11 | const [metrics, setMetrics] = useState(0); 12 | const [intervalId, setIntervalId] = useState(null); 13 | const [clusterName, setClusterName] = useState(''); 14 | const [cluster, setCluster] = useState({}); 15 | const params = useParams(); 16 | useEffect(() => { 17 | axios.get(`/cluster/clusterById/${params.id}`).then((res) => { 18 | setCluster(res.data); 19 | console.log(res.data); 20 | }); 21 | }, [clusterName]); 22 | 23 | switch (currentTab) { 24 | case 'overview': 25 | return ( 26 | 31 | ); 32 | /* 33 | cluster, 34 | setUpdatingCluster, 35 | clusterId, 36 | setCluster, 37 | setIntervalId, 38 | */ 39 | case 'health': 40 | return ( 41 | 46 | ); 47 | 48 | case 'topic': 49 | return ( 50 | 55 | ); 56 | default: 57 | return
default
; 58 | } 59 | }; 60 | 61 | export default ClusterDynamicDetails; 62 | -------------------------------------------------------------------------------- /src/client/components/ClusterOverview.jsx: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { useEffect, useState } from 'react'; 3 | import CpuMetrics from '../allMetrics/CpuMetrics'; 4 | import RamMetrics from '../allMetrics/RamMetrics'; 5 | import BytesMetrics from '../allMetrics/BytesMetrics'; 6 | import { 7 | Box, 8 | Button, 9 | Modal, 10 | Typography, 11 | Card, 12 | TextField, 13 | CardContent, 14 | } from '@mui/material'; 15 | import { useNavigate } from 'react-router-dom'; 16 | import NetworkMetrics from '../allMetrics/NetworkMetrics'; 17 | import CreateAlertModal from './CreateAlertModal'; 18 | 19 | const styles = { 20 | root: { 21 | display: 'flex', 22 | flexDirection: 'column', 23 | alignItems: 'center', 24 | justifyContent: 'center', 25 | height: '100vh', 26 | 27 | backgroundSize: 'cover', 28 | backgroundPosition: 'center', 29 | overflow: 'hidden', 30 | }, 31 | card: { 32 | backgroundColor: 'white', 33 | borderRadius: '8px', 34 | width: '100%', 35 | boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.1)', 36 | padding: '16px', 37 | }, 38 | input: { 39 | marginBottom: '16px', 40 | }, 41 | submitButton: { 42 | marginTop: '16px', 43 | marginBottom: '8px', 44 | backgroundColor: '#227BA5', 45 | color: '#ffffff', 46 | '&:hover': { 47 | backgroundColor: '#1D6490', 48 | }, 49 | }, 50 | signupLink: { 51 | color: '##227BA5', 52 | textDecoration: 'none', 53 | '&:hover': { 54 | textDecoration: 'underline', 55 | }, 56 | cursor: 'pointer', 57 | }, 58 | }; 59 | 60 | const ClusterOverview = ({ 61 | cluster, 62 | setUpdatingCluster, 63 | clusterId, 64 | setCluster, 65 | setIntervalId, 66 | }) => { 67 | const [metrics, setMetrics] = useState(0); 68 | const [showModal, setShowModal] = useState(false); 69 | const [name, setName] = useState(cluster?.name); 70 | const [promUrl, setPromUrl] = useState(cluster?.prometheusUrl); 71 | const [deleteModal, setDeleteModal] = useState(false); 72 | const [confirmDeleteName, setConfirmDeleteName] = useState(''); 73 | const [showAlertModal, setShowAlertModal] = useState(false); 74 | 75 | const navigate = useNavigate(); 76 | useEffect(() => { 77 | if (!cluster) return; 78 | 79 | setName(cluster.name); 80 | setPromUrl(cluster.prometheusUrl); 81 | }, [cluster]); 82 | 83 | useEffect(() => { 84 | if (!cluster) return; 85 | const id = setInterval(() => { 86 | axios 87 | .post('/jmx/metrics', { 88 | broker: cluster.prometheusUrl, 89 | userId: localStorage.getItem('userId'), 90 | }) 91 | .then((res) => { 92 | setMetrics(res.data); 93 | console.log(res.data); 94 | }); 95 | }, 5000); 96 | setIntervalId(id); 97 | return () => clearInterval(id); 98 | }, [cluster]); 99 | 100 | const handleUpdateCluster = async () => { 101 | try { 102 | setUpdatingCluster(true); 103 | const response = await axios.patch('/cluster', { 104 | cluster_id: cluster._id, 105 | cluster_name: name, 106 | prom_port: promUrl, 107 | }); 108 | setCluster(response.data); 109 | 110 | setUpdatingCluster(false); 111 | 112 | setShowModal(false); 113 | } catch (e) { 114 | console.error('Error in ClusterOverview.jsx: Line 82'); 115 | } 116 | }; 117 | const handleDeleteCluster = async () => { 118 | if (cluster.name !== confirmDeleteName) { 119 | return; 120 | } 121 | try { 122 | setUpdatingCluster(true); 123 | const response = await axios.delete(`/cluster/${cluster._id}`); 124 | 125 | setDeleteModal(false); 126 | window.location.reload(); 127 | } catch (e) { 128 | console.error( 129 | `Error in handleUpdateCluster function in Cluster Overview.jsx: ${e}` 130 | ); 131 | } 132 | }; 133 | 134 | return !cluster ? ( 135 |
136 | ) : ( 137 | <> 138 | 142 | 143 | 151 | 159 | 167 | 175 | 185 | 186 | 187 | 193 | Edit Cluster 194 | 195 | setName(e.target.value)} 203 | /> 204 | setPromUrl(e.target.value)} 214 | /> 215 | 221 | 222 | 229 | 230 | 231 | 232 | 233 | 243 | 253 | 254 | 255 | 261 | Delete Cluster 262 | 263 | setConfirmDeleteName(e.target.value)} 271 | /> 272 | 273 | 280 | 281 | 293 | 294 | 295 | 296 | 297 | 298 |
308 | 309 | 310 | 314 | 315 | 316 | 317 | 318 | 319 |
320 | 321 | ); 322 | }; 323 | 324 | export default ClusterOverview; 325 | -------------------------------------------------------------------------------- /src/client/components/CreateAlertModal.jsx: -------------------------------------------------------------------------------- 1 | import { AlignHorizontalRight } from '@mui/icons-material'; 2 | import { 3 | Box, 4 | List, 5 | ListItem, 6 | ListItemButton, 7 | ListItemText, 8 | FormControl, 9 | InputLabel, 10 | MenuItem, 11 | Select, 12 | Typography, 13 | TextField, 14 | Button, 15 | Card, 16 | CardContent, 17 | Modal, 18 | } from '@mui/material'; 19 | import axios from 'axios'; 20 | import { useEffect, useState } from 'react'; 21 | 22 | function MyAlerts({ setShowMyAlerts }) { 23 | const alertNames = { 24 | latency: 'Latency (ms)', 25 | cpumetric: 'CPU Usage', 26 | bytesintotalmetric: 'Total Bytes In', 27 | bytesOutmetric: 'Total Bytes Out', 28 | ramUsageMetric: 'Ram Usage (MB)', 29 | producerRequestsTotal: 'Total Producer Requests', 30 | producersMessagesInTotal: 'Total Producer Messages', 31 | producerConversionsTotal: 'Total Producer Conversions', 32 | consumerRequestsTotal: 'Total Consumer Requests', 33 | consumerConversionsTotal: 'Total Consumer Requests', 34 | consumerFailedRequestsTotal: 'Total Failed Consumer Requests', 35 | }; 36 | const [alerts, setAlerts] = useState([]); 37 | 38 | useEffect(() => { 39 | axios 40 | .get('/alerts/' + localStorage.getItem('userId')) 41 | .then((res) => { 42 | console.log({ alerts }); 43 | setAlerts(res.data); 44 | }) 45 | .catch((e) => console.log(e)); 46 | }, [alerts]); 47 | 48 | const styles = { 49 | card: { 50 | backgroundColor: 'white', 51 | borderRadius: '8px', 52 | width: '100%', 53 | boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.1)', 54 | padding: '16px', 55 | }, 56 | }; 57 | 58 | const deleteAlert = (id) => { 59 | axios.delete('/alerts/' + id); 60 | }; 61 | 62 | return ( 63 | 71 | 72 | 73 | 80 | My Alerts 81 | 82 | 83 | 84 | {!alerts.length ? ( 85 | No Alerts Here! 86 | ) : ( 87 | alerts.map((alert) => ( 88 | 89 | 99 | 107 | 108 | )) 109 | )} 110 | 111 | 112 | 113 | 114 | ); 115 | } 116 | 117 | export default function CreateAlertModal({ setShowModal, showModal }) { 118 | const styles = { 119 | card: { 120 | backgroundColor: 'white', 121 | borderRadius: '8px', 122 | width: '100%', 123 | boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.1)', 124 | padding: '16px', 125 | }, 126 | }; 127 | const [low, setLow] = useState(-Infinity); 128 | const [high, setHigh] = useState(Infinity); 129 | const [metric, setMetric] = useState('latency'); 130 | const [showMyAlerts, setShowMyAlerts] = useState(false); 131 | 132 | const createAlert = async () => { 133 | const body = { 134 | under: low, 135 | over: high, 136 | id: localStorage.getItem('userId'), 137 | metric, 138 | }; 139 | 140 | const res = await axios.post('/alerts', body).catch((e) => { 141 | console.log(e); 142 | }); 143 | }; 144 | 145 | return showMyAlerts ? ( 146 | 147 | ) : ( 148 | 158 | 159 | 160 | 167 | 174 | Create Alert 175 | 176 | 177 | Metric 178 | 210 | 211 | 212 | setLow(e.target.value)} 218 | /> 219 | setHigh(e.target.value)} 225 | /> 226 | 227 | 233 | 234 | 241 | 242 | 243 | 244 | 245 | ); 246 | } 247 | -------------------------------------------------------------------------------- /src/client/components/Doc.jsx: -------------------------------------------------------------------------------- 1 | import { Box, Container, Grid, Paper, Typography, List, ListItem, ListItemText, Divider } from "@mui/material"; 2 | 3 | const Doc = () => { 4 | return ( 5 | 6 | 7 | 8 | User Routes 9 | 10 | 11 | 12 | 13 | 14 | /user/login 15 | 16 | 17 | Logs user in. 18 | 19 | 20 | Required Fields: 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
 38 |             {`Request
 39 |             {
 40 |               "email": "johndoe@example.com",
 41 |               "password": "password123",
 42 |             }
 43 |             `}
 44 |               
45 |
 46 |             {`Response
 47 |             {
 48 |               "id":123,
 49 |               "email": "johndoe@example.com,
 50 |               "name": "John Doe"
 51 |             }`}
 52 |               
53 |
54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | /user/signup 64 | 65 | 66 | Create a new user account. 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
 84 |                 {`Request
 85 |             {
 86 |               "email": "johndoe@example.com",
 87 |               "password": "password123",
 88 |               "name": "John Doe"
 89 |             }`}
 90 |               
91 |
 92 |                 {`Response
 93 |             {
 94 |               "success": true,
 95 |             }`}
 96 |               
97 |
98 |
99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | /user/:id 108 | 109 | 110 | Get user information by ID. 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 |
122 |                 {`Request
123 |             {
124 |               "id": 123,
125 |             }`}
126 |               
127 |
128 |                 {`Response
129 |             {
130 |               "id": 123,
131 |               "email": "johndoe@example.com",
132 |               "name": "John Doe"
133 |             }`}
134 |               
135 |
136 | 137 |
138 | 139 |
140 | 141 | 142 | 143 | Kafka Router 144 | 145 | 146 | 147 | 148 | 149 | /kafka/:id 150 | 151 | 152 | Get clusters associated with a user ID. 153 | 154 | 155 | Required Fields: 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 |
169 |                 {`Request
170 |             {
171 |               "id": 123,
172 |             }`}
173 |               
174 |
175 |                 {`Response
176 |             [
177 |                 {
178 |                     "id": 1,
179 |                     "name": "cluster1",
180 |                     "user_id": 123
181 |                   },
182 |                   {
183 |                     "id": 2,
184 |                     "name": "cluster2",
185 |                     "user_id": 123
186 |                   }
187 |             ]`}
188 |               
189 |
190 |
191 | 192 | 193 | 194 | 195 | 196 | 197 | /kafka/clusterById/:id 198 | 199 | 200 | Get cluster information by ID. 201 | 202 | 203 | Required Fields: 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 |
217 |         {`Request
218 |             {
219 |                 "id": 1
220 |             }`}
221 |       
222 |
223 |         {`Response
224 | {
225 |   "id": 1,
226 |   "name": "cluster1",
227 |   "created_at": "2022-05-02T15:44:59.000Z",
228 |   "user_id": 123
229 | }`}
230 |       
231 |
232 |
233 |
234 | 235 | 236 | 237 | 238 | Metrics Router 239 | 240 | 241 | 242 | 243 | 244 | /metrics 245 | 246 | 247 | Get cluster overview metrics associated with a Prometheus port. 248 | 249 | 250 | Required Fields: 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 |
264 |                 {`Request
265 |             {
266 |               "id": 123,
267 |             }`}
268 |               
269 |
270 |                 {`Response
271 |             [
272 |                 {
273 |                     "id": 1,
274 |                     "name": "cluster1",
275 |                     "user_id": 123
276 |                   },
277 |                   {
278 |                     "id": 2,
279 |                     "name": "cluster2",
280 |                     "user_id": 123
281 |                   }
282 |             ]`}
283 |               
284 |
285 |
286 | 287 | 288 | 289 | 290 | 291 | 292 | /metrics/broker 293 | 294 | 295 | Get cluster metrics information by ID. 296 | 297 | 298 | Required Fields: 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 |
312 |         {`Request
313 |             {
314 |                 "id": 1
315 |             }`}
316 |       
317 |
318 |         {`Response
319 |           {
320 |             "id": 1,
321 |             "name": "cluster1",
322 |             "created_at": "2022-05-02T15:44:59.000Z",
323 |             "user_id": 123
324 |           }`}
325 |       
326 |
327 |
328 |
329 | 330 | 331 | 332 | 333 | Cluster Router 334 | 335 | 336 | 337 | 338 | 339 | /cluster 340 | 341 | 342 | Get cluster overview metrics associated with a Prometheus port. 343 | 344 | 345 | Required Fields: 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 |
359 |                 {`Request
360 |             {
361 |               "id": 123,
362 |             }`}
363 |               
364 |
365 |                 {`Response
366 |             [
367 |                 {
368 |                     "id": 1,
369 |                     "name": "cluster1",
370 |                     "user_id": 123
371 |                   },
372 |                   {
373 |                     "id": 2,
374 |                     "name": "cluster2",
375 |                     "user_id": 123
376 |                   }
377 |             ]`}
378 |               
379 |
380 |
381 | 382 | 383 | 384 | 385 | 386 | 387 | /metrics/broker 388 | 389 | 390 | Get cluster metrics information by ID. 391 | 392 | 393 | Required Fields: 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 |
407 |         {`Request
408 |             {
409 |                 "id": 1
410 |             }`}
411 |       
412 |
413 |         {`Response
414 |           {
415 |             "id": 1,
416 |             "name": "cluster1",
417 |             "created_at": "2022-05-02T15:44:59.000Z",
418 |             "user_id": 123
419 |           }`}
420 |       
421 |
422 |
423 |
424 | 425 |
426 |
427 | )} 428 | 429 | export default Doc; -------------------------------------------------------------------------------- /src/client/components/DynamicHealth.jsx: -------------------------------------------------------------------------------- 1 | import { Grid, Box, Paper, Card, Typography } from '@mui/material'; 2 | import Chart from 'chart.js'; 3 | import ConsumerConvMetrics from '../allMetrics/consumerConvMetrics'; 4 | import ConsumerFailReqMetrics from '../allMetrics/consumerFailReqMetrics'; 5 | import ConsumerReqMetrics from '../allMetrics/consumerReqMetrics'; 6 | import ProducerConvMetrics from '../allMetrics/producerConvMetrics'; 7 | import ProducerMessMetrics from '../allMetrics/producerMessMetrics'; 8 | import ProducerReqMetrics from '../allMetrics/producerReqMetrics'; 9 | import { useState, useEffect } from 'react'; 10 | import axios from 'axios'; 11 | const containerStyle = { 12 | padding: '20px', 13 | margin: 'auto', 14 | marginLeft: 150, 15 | marginTop: 50, 16 | }; 17 | 18 | const chartsContainerStyle = { 19 | display: 'flex', 20 | justifyContent: 'center', 21 | gap: '40px', 22 | flexWrap: 'wrap', 23 | padding: '16px', 24 | alignItems: 'center', 25 | }; 26 | 27 | const chartStyle = { 28 | width: '40%', 29 | height: '300px', 30 | marginBottom: '16px', 31 | marginRight: '30px', 32 | }; 33 | 34 | const metricsBoxStyle = { 35 | flex: 1, 36 | padding: '5px', 37 | width: '60%', 38 | marginLeft: '20px', 39 | }; 40 | 41 | const HealthMetrics = ({ cluster, intervalId, setIntervalId }) => { 42 | const [moreMetrics, setMoreMetrics] = useState(0); 43 | useEffect(() => { 44 | if (!cluster) return; 45 | const id = setInterval(() => { 46 | axios 47 | .post('/jmx/producerConsumerMetrics', { 48 | broker: cluster.prometheusUrl, 49 | userId: localStorage.getItem('userId'), 50 | }) 51 | .then((res) => { 52 | setMoreMetrics(res.data); 53 | console.log(res.data); 54 | }); 55 | }, 5000); 56 | setIntervalId(id); 57 | return () => clearInterval(id); 58 | }, [cluster]); 59 | return ( 60 |
61 | 62 | 63 | 64 | 67 | 68 | 69 | 72 | 73 | 74 | 77 | 78 | 79 | 82 | 83 | 84 | 87 | 88 | 89 | 92 | 93 | 94 | 95 | 105 | 106 | Consumer Requests 107 | 108 | {Math.round(moreMetrics.consumerRequestsTotal * 10) / 10} 109 | 110 | 111 | 112 | Failed Requests 113 | 114 | {Math.round(moreMetrics.consumerFailedRequestsTotal * 10) / 10} 115 | 116 | 117 | 118 | Consumer Conversions 119 | 120 | {Math.round(moreMetrics.consumerConversionsTotal * 10) / 10} 121 | 122 | 123 | 124 | Producer Requests 125 | 126 | {Math.round(moreMetrics.producerRequestsTotal * 10) / 10} 127 | 128 | 129 | 130 | Producer Messages 131 | 132 | {Math.round(moreMetrics.producersMessagesInTotal * 10) / 10} 133 | 134 | 135 | 136 | Producer Conversions 137 | 138 | {Math.round(moreMetrics.producerConversionsTotal * 10) / 10} 139 | 140 | 141 | 142 | 143 | 144 |
145 | ); 146 | }; 147 | 148 | export default HealthMetrics; 149 | -------------------------------------------------------------------------------- /src/client/components/DynamicOverview.jsx: -------------------------------------------------------------------------------- 1 | import { Box, Paper, Card, Typography } from '@mui/material'; 2 | import Chart from 'chart.js'; 3 | import CpuMetrics from '../allMetrics/CpuMetrics'; 4 | import RamMetrics from '../allMetrics/RamMetrics'; 5 | import NetworkMetrics from '../allMetrics/NetworkMetrics'; 6 | import BytesMetrics from '../allMetrics/BytesMetrics'; 7 | import { useState, useEffect } from 'react'; 8 | import axios from 'axios'; 9 | const containerStyle = { 10 | padding: '20px', 11 | margin: 'auto', 12 | 13 | marginLeft: 150, 14 | marginTop: 50, 15 | }; 16 | 17 | const chartsContainerStyle = { 18 | display: 'flex', 19 | justifyContent: 'center', 20 | gap: '40px', 21 | flexWrap: 'wrap', 22 | padding: '16px', 23 | 24 | alignItems: 'center', 25 | }; 26 | 27 | const chartStyle = { 28 | width: '40%', 29 | height: '300px', 30 | marginBottom: '16px', 31 | marginRight: '30px', 32 | }; 33 | 34 | const metricsBoxStyle = { 35 | flex: 1, 36 | padding: '5px', 37 | width: '60%', 38 | marginLeft: '20px', 39 | }; 40 | 41 | const DynamicOverview = ({ cluster, intervalId, setIntervalId }) => { 42 | const [metrics, setMetrics] = useState(0); 43 | useEffect(() => { 44 | if (!cluster) return; 45 | const id = setInterval(() => { 46 | axios 47 | .post('/jmx/metrics', { 48 | broker: cluster.prometheusUrl, 49 | userId: localStorage.getItem('userId'), 50 | }) 51 | .then((res) => { 52 | setMetrics(res.data); 53 | }); 54 | }, 5000); 55 | setIntervalId(id); 56 | return () => clearInterval(id); 57 | }, [cluster]); 58 | 59 | return ( 60 | 61 | 62 | 63 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 89 | 90 | CPU 91 | 92 | {Number((metrics.cpumetric * 100).toFixed(2))}% 93 | 94 | 95 | 96 | Ping 97 | 98 | {Math.round(Number(metrics.latency))}ms 99 | 100 | 101 | 102 | RAM 103 | 104 | {Math.round(Number(metrics.ramUsageMetric) / 1000)}mb 105 | 106 | 107 | {/* 108 | Topics 109 | 110 | {Math.round(Number(metrics.ramUsageMetric) / 1000)}mb 111 | 112 | */} 113 | 114 | 115 | 116 | ); 117 | }; 118 | 119 | export default DynamicOverview; 120 | -------------------------------------------------------------------------------- /src/client/components/DynamicTopics.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Paper, 3 | Typography, 4 | TextField, 5 | Accordion, 6 | AccordionDetails, 7 | AccordionSummary, 8 | Box, 9 | } from '@mui/material'; 10 | import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; 11 | import React, { useState, useEffect } from 'react'; 12 | import TopicDetail from './TopicDetail'; 13 | import axios from 'axios'; 14 | const DynamicTopics = ({ cluster }) => { 15 | const [expanded, setExpanded] = useState(false); 16 | const handleChange = (panel) => (event, isExpanded) => { 17 | setExpanded(isExpanded ? panel : false); 18 | }; 19 | const [topics, setTopics] = useState({ 20 | resultType: 'vector', 21 | result: [ 22 | { 23 | metric: { 24 | topic: '__consumer_offsets', 25 | }, 26 | value: [1682963429.198, '50'], 27 | }, 28 | ], 29 | }); 30 | 31 | useEffect(() => { 32 | axios 33 | .post('/jmx/topics', { 34 | broker: cluster.prometheusUrl, 35 | }) 36 | .then((response) => { 37 | setTopics(response.data); 38 | console.log(response.data); 39 | }) 40 | .catch((e) => console.log(e)); 41 | }, []); 42 | const [searchText, setSearchText] = useState(''); 43 | return ( 44 | 54 | 55 | {' '} 56 | Search Topic 57 | 58 | setSearchText(e.target.value)} 62 | /> 63 | 73 | {topics.data?.result 74 | .filter((value) => { 75 | if (searchText === '') { 76 | console.log(value); 77 | return value; 78 | } else if ( 79 | value.metric.topic 80 | .toLowerCase() 81 | .includes(searchText.toLowerCase()) 82 | ) { 83 | console.log(value); 84 | return value; 85 | } 86 | }) 87 | .map((topic, index) => { 88 | return ( 89 | 100 | } 102 | aria-controls={`${topic?.metric.topic}${index}-content`} 103 | > 104 | 111 | {topic?.metric.topic} 112 | 113 | 114 | 115 | 116 | 117 | 118 | ); 119 | })} 120 | 121 | 122 | ); 123 | }; 124 | 125 | export default DynamicTopics; 126 | -------------------------------------------------------------------------------- /src/client/components/EditCluster.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useEffect, useState } from 'react'; 3 | import { Modal } from "@mui/material"; 4 | import Typography from "@mui/material/Typography"; 5 | import {Popper, Button, Box} from '@mui/material'; 6 | 7 | 8 | const EditCluster = ({anchorEl}) => { 9 | const [showPopper, setShowPopper] = useState(false); 10 | 11 | return ( 12 | <> 13 | 14 | 15 | 16 | The content of the Popper. 17 | 18 | 19 | 20 | ) 21 | }; 22 | 23 | export default EditCluster; -------------------------------------------------------------------------------- /src/client/components/HomePageSideBar.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { styled, useTheme } from '@mui/material/styles'; 3 | import Box from '@mui/material/Box'; 4 | import Drawer from '@mui/material/Drawer'; 5 | import CssBaseline from '@mui/material/CssBaseline'; 6 | import MuiAppBar from '@mui/material/AppBar'; 7 | import Toolbar from '@mui/material/Toolbar'; 8 | import List from '@mui/material/List'; 9 | import Typography from '@mui/material/Typography'; 10 | import Divider from '@mui/material/Divider'; 11 | import ListItem from '@mui/material/ListItem'; 12 | import ListItemButton from '@mui/material/ListItemButton'; 13 | import ListItemIcon from '@mui/material/ListItemIcon'; 14 | import ListItemText from '@mui/material/ListItemText'; 15 | import { Modal } from '@mui/material'; 16 | import { useState, useEffect } from 'react'; 17 | import { Button } from '@mui/material'; 18 | import { Paper } from '@mui/material'; 19 | import { Card, CardContent, TextField } from '@mui/material'; 20 | import axios from 'axios'; 21 | import ClusterOverview from './ClusterOverview'; 22 | import SchemaIcon from '@mui/icons-material/Schema'; 23 | import LogoutButton from './LogoutButton'; 24 | import AddIcon from '@mui/icons-material/Add'; 25 | import orbitalLogo from '../assets/transparent-orbital.png'; 26 | 27 | const drawerWidth = 100; 28 | 29 | const styles = { 30 | paper: { 31 | backgroundColor: 'black', 32 | }, 33 | card: { 34 | backgroundColor: 'white', 35 | borderRadius: '8px', 36 | width: '100%', 37 | boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.1)', 38 | padding: '16px', 39 | }, 40 | input: { 41 | marginBottom: '16px', 42 | }, 43 | submitButton: { 44 | marginTop: '16px', 45 | marginBottom: '8px', 46 | backgroundColor: '#227BA5', 47 | color: '#ffffff', 48 | '&:hover': { 49 | backgroundColor: '#1D6490', 50 | }, 51 | }, 52 | signupLink: { 53 | color: '##227BA5', 54 | textDecoration: 'none', 55 | '&:hover': { 56 | textDecoration: 'underline', 57 | }, 58 | cursor: 'pointer', 59 | }, 60 | }; 61 | const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })( 62 | ({ theme, open }) => ({ 63 | flexGrow: 1, 64 | padding: theme.spacing(3), 65 | transition: theme.transitions.create('margin', { 66 | easing: theme.transitions.easing.sharp, 67 | duration: theme.transitions.duration.leavingScreen, 68 | }), 69 | marginLeft: `-${drawerWidth}px`, 70 | ...(open && { 71 | transition: theme.transitions.create('margin', { 72 | easing: theme.transitions.easing.easeOut, 73 | duration: theme.transitions.duration.enteringScreen, 74 | }), 75 | backgroundColor: 'rgb(24, 45, 91)', 76 | marginLeft: 0, 77 | }), 78 | }) 79 | ); 80 | 81 | const AppBar = styled(MuiAppBar, { 82 | shouldForwardProp: (prop) => prop !== 'open', 83 | })(({ theme, open }) => ({ 84 | transition: theme.transitions.create(['margin', 'width'], { 85 | easing: theme.transitions.easing.sharp, 86 | duration: theme.transitions.duration.leavingScreen, 87 | }), 88 | 89 | backgroundColor: '#444444', 90 | ...(open && { 91 | width: `calc(100% - ${drawerWidth}px)`, 92 | marginLeft: `${drawerWidth}px`, 93 | transition: theme.transitions.create(['margin', 'width'], { 94 | easing: theme.transitions.easing.easeOut, 95 | duration: theme.transitions.duration.enteringScreen, 96 | }), 97 | backgroundColor: '#444444', 98 | }), 99 | })); 100 | 101 | const DrawerHeader = styled('div')(({ theme }) => ({ 102 | display: 'flex', 103 | alignItems: 'center', 104 | padding: theme.spacing(0, 1), 105 | // necessary for content to be below app bar 106 | ...theme.mixins.toolbar, 107 | justifyContent: 'flex-end', 108 | backgroundColor: '#444444', 109 | })); 110 | 111 | export default function PersistentDrawerLeft({ user }) { 112 | const theme = useTheme(); 113 | const [open, setOpen] = useState(true); 114 | const [showModal, setShowModal] = useState(false); 115 | const [clusterName, setClusterName] = useState(''); 116 | const [brokers, setBrokers] = useState(''); 117 | const [currentCluster, setCurrentCluster] = useState(null); 118 | const [currentIndex, setCurrentIndex] = useState(0); 119 | const [userClusters, setUserClusters] = useState([]); 120 | const [updatingCluster, setUpdatingCluster] = useState(false); 121 | const [intervalId, setIntervalId] = useState(null); 122 | const [currentTab, setCurrentTab] = useState('overview'); 123 | 124 | const handleClose = () => { 125 | setShowModal(false); 126 | }; 127 | const handleOpen = () => { 128 | setShowModal(true); 129 | }; 130 | 131 | // Get array of clusters based on userID 132 | useEffect(() => { 133 | console.log('useEffect GETS CLUSTER ARRAY fired.'); 134 | try { 135 | axios 136 | .get(`/cluster/${localStorage.getItem('userId')}`) 137 | .then((response) => { 138 | setUserClusters(response.data); 139 | console.log(response.data); 140 | }); 141 | } catch (e) { 142 | console.error('Error in HomePageSideBar.jsx: Lines 143 - 155'); 143 | } 144 | }, [showModal, updatingCluster]); 145 | 146 | const handleCreateCluster = async () => { 147 | await axios.post('/cluster', { 148 | cluster_name: clusterName, 149 | prom_port: brokers, 150 | owner: localStorage.getItem('userId'), 151 | }); 152 | setShowModal(false); 153 | }; 154 | 155 | return ( 156 | 163 | 164 | 169 | 170 | 176 | {currentCluster?.name || 'Select a cluster'} 177 | 178 | 179 | 180 | 191 | 198 | Orbital logo 208 | 218 | {'Orbital'} 219 | 220 | 221 | 222 | 223 | 224 | 225 | 229 | 230 | 231 | 232 | 233 | 234 | 243 | {userClusters.map((cluster, index) => { 244 | const icon = ; 245 | 246 | return ( 247 | <> 248 | 255 | { 263 | setCurrentCluster(cluster); 264 | setCurrentIndex(index); 265 | clearInterval(intervalId); 266 | }} 267 | > 268 | {icon} 269 | 270 | 276 | {cluster.name} 277 | 278 | 279 | 280 | 281 | 282 | ); 283 | })} 284 | 285 | 286 | 287 |
288 | 289 | 295 | {showModal && ( 296 | 307 | 308 | 309 | 316 | Create Cluster 317 | 318 | setClusterName(e.target.value)} 325 | /> 326 | setBrokers(e.target.value)} 335 | /> 336 | 345 | 346 | 347 | 348 | )} 349 | 350 | 357 |
358 |
359 | ); 360 | } 361 | -------------------------------------------------------------------------------- /src/client/components/LandingPageDemo.jsx: -------------------------------------------------------------------------------- 1 | import { Box, Container, Grid, Typography, Divider } from "@mui/material"; 2 | import Cluster from "../assets/Cluster.gif"; 3 | import Details from "../assets/More-Details.gif"; 4 | import topics from "../assets/topics.gif"; 5 | 6 | const DemoSection = () => { 7 | 8 | return ( 9 | 19 | 20 | 21 | 22 | 23 | Demo 24 | 25 | 26 | See our quick demo to learn how to use Orbital to monitor your Apache Kafka clusters. 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Demo Gif Here 36 | Demo Gif Here 37 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default DemoSection; 47 | -------------------------------------------------------------------------------- /src/client/components/LandingPageHero.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box, Typography, Button, Container, Card, CardMedia } from '@mui/material'; 3 | import { Grid } from '@mui/material'; 4 | import {useNavigate} from 'react-router-dom'; 5 | import Space from '../assets/Space.jpg'; 6 | import TransparentTextLogo from '../assets/Transparent-Logo.png'; 7 | 8 | const LandingPageHero = () => { 9 | const navigate = useNavigate() 10 | return ( 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Orbital 31 | 32 | 33 | The ultimate solution for monitoring Apache Kafka clusters. Get real-time insights and alerts on key metrics to optimize performance and troubleshoot issues quickly. 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ); 51 | }; 52 | 53 | export default LandingPageHero; -------------------------------------------------------------------------------- /src/client/components/LandingPageTeam.jsx: -------------------------------------------------------------------------------- 1 | import { Box, Container, Grid, Typography, Avatar } from "@mui/material"; 2 | import vicheadshot from "../assets/vic-pic.jpeg"; 3 | import sidheadshot from "../assets/Sid-pic.jpeg"; 4 | import slavaheadshot from "../assets/Slava.jpg"; 5 | import kirillheadshot from "../assets/Kirill.jpeg"; 6 | 7 | 8 | const LoginTechStack = () => { 9 | return ( 10 | 11 | 12 | 13 | Meet the Team 14 | 15 | 16 | window.open('https://www.linkedin.com/in/kirill-karbutov/')}> 17 | 18 | 19 | 20 | Kirill 21 | 22 | window.open('https://www.linkedin.com/in/sidney-brodsky/')} > 23 | 24 | 25 | 26 | Sid 27 | 28 | window.open('https://www.linkedin.com/in/slava-melikov/')}> 29 | 30 | 31 | 32 | Slava 33 | 34 | window.open('https://www.linkedin.com/in/vic-gul/')}> 35 | 36 | 37 | 38 | Victor 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | }; 46 | 47 | export default LoginTechStack; -------------------------------------------------------------------------------- /src/client/components/LandingPageTechStack.jsx: -------------------------------------------------------------------------------- 1 | import { Box, Container, Grid, Typography } from "@mui/material"; 2 | import reactLogo from "../assets/react-logo.png"; 3 | import muiLogo from "../assets/mui-logo.jpg"; 4 | import chartjsLogo from "../assets/chartjs-logo.jpg"; 5 | import expressLogo from "../assets/express-logo.jpg"; 6 | import mongodbLogo from "../assets/mongodb-logo.jpg"; 7 | import prometheusLogo from "../assets/prometheus-logo.png"; 8 | 9 | const LoginTechStack = () => { 10 | return ( 11 | 12 | 13 | 14 | Our Tech Stack 15 | 16 | 17 | 18 | 19 | React logo 20 | 21 | React 22 | 23 | 24 | 25 | Material UI logo 26 | 27 | Material UI 28 | 29 | 30 | 31 | ChartJS logo 32 | 33 | ChartJS 34 | 35 | 36 | 37 | Express logo 38 | 39 | Express 40 | 41 | 42 | 43 | MongoDB logo 44 | 45 | MongoDB 46 | 47 | 48 | 49 | Prometheus logo 50 | 51 | Prometheus 52 | 53 | 54 | 55 | 56 | ); 57 | }; 58 | 59 | export default LoginTechStack; -------------------------------------------------------------------------------- /src/client/components/LogoutButton.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@mui/material"; 2 | import { useNavigate } from "react-router-dom"; 3 | 4 | export default function LogoutButton() { 5 | const nav = useNavigate(); 6 | 7 | const handleLogout = () => { 8 | localStorage.clear(); 9 | nav('/login'); 10 | } 11 | 12 | return ( 13 | 23 | ) 24 | } -------------------------------------------------------------------------------- /src/client/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AppBar from '@mui/material/AppBar'; 3 | import Box from '@mui/material/Box'; 4 | import Toolbar from '@mui/material/Toolbar'; 5 | import IconButton from '@mui/material/IconButton'; 6 | import Typography from '@mui/material/Typography'; 7 | import Menu from '@mui/material/Menu'; 8 | import MenuIcon from '@mui/icons-material/Menu'; 9 | import Container from '@mui/material/Container'; 10 | import Avatar from '@mui/material/Avatar'; 11 | import Button from '@mui/material/Button'; 12 | import Tooltip from '@mui/material/Tooltip'; 13 | import MenuItem from '@mui/material/MenuItem'; 14 | import AdbIcon from '@mui/icons-material/Adb'; 15 | import {useNavigate} from 'react-router-dom' 16 | import orbitalLogo from '../assets/transparent-orbital.png'; 17 | 18 | const settings = ['Profile', 'Account', 'Dashboard', 'Logout']; 19 | const pagess = [{name: "Docs", url: "/docs"}] 20 | 21 | function Navbar() { 22 | const navigate = useNavigate() 23 | const [anchorElNav, setAnchorElNav] = React.useState(null); 24 | const [anchorElUser, setAnchorElUser] = React.useState(null); 25 | 26 | const handleOpenNavMenu = (event) => { 27 | setAnchorElNav(event.currentTarget); 28 | }; 29 | const handleOpenUserMenu = (event) => { 30 | setAnchorElUser(event.currentTarget); 31 | }; 32 | 33 | const handleCloseNavMenu = () => { 34 | setAnchorElNav(null); 35 | }; 36 | 37 | const handleCloseUserMenu = () => { 38 | setAnchorElUser(null); 39 | }; 40 | 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | Orbital logo 48 | 49 | 50 | 65 | Orbital 66 | 67 | 68 | 69 | 77 | 78 | 79 | 97 | {pagess.map((page) => ( 98 | 99 | navigate(page.url)}>{page.name} 100 | 101 | ))} 102 | 103 | 104 | 105 | 121 | ORBITAL 122 | 123 | 124 | {pagess.map((page) => ( 125 | 132 | ))} 133 | 134 | 135 | 136 | 137 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | ); 147 | } 148 | export default Navbar; 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/client/components/TopicDetail.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useEffect, useState } from 'react'; 3 | import { Typography, Box, Divider } from '@mui/material'; 4 | import axios from 'axios'; 5 | const TopicDetail = ({ topic, cluster }) => { 6 | const [topicMetric, setTopicMetric] = useState({}); 7 | console.log(cluster); 8 | useEffect(() => { 9 | axios 10 | .post('/jmx/topicMetrics', { 11 | broker: cluster.prometheusUrl, 12 | topic: topic, 13 | }) 14 | .then((response) => { 15 | console.log(response.data.data); 16 | setTopicMetric(response.data); 17 | }); 18 | }, []); 19 | return ( 20 | 21 | {topicMetric.data?.result.map((metric) => { 22 | return ( 23 | 24 | 25 | 26 | Partition: {`${metric?.metric.partition}`} 27 | 28 | 29 | Offset: {`${metric?.value[1]}`} 30 | 31 | 32 | ); 33 | })} 34 | 35 | ); 36 | }; 37 | 38 | export default TopicDetail; 39 | -------------------------------------------------------------------------------- /src/client/pages/ClusterDetail.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { styled } from '@mui/material/styles'; 3 | import Box from '@mui/material/Box'; 4 | import Drawer from '@mui/material/Drawer'; 5 | import CssBaseline from '@mui/material/CssBaseline'; 6 | import MuiAppBar from '@mui/material/AppBar'; 7 | import Toolbar from '@mui/material/Toolbar'; 8 | import List from '@mui/material/List'; 9 | import Typography from '@mui/material/Typography'; 10 | import Divider from '@mui/material/Divider'; 11 | import ListItem from '@mui/material/ListItem'; 12 | import ListItemButton from '@mui/material/ListItemButton'; 13 | import ListItemIcon from '@mui/material/ListItemIcon'; 14 | import { useState } from 'react'; 15 | import { Button } from '@mui/material'; 16 | import LogoutButton from '../components/LogoutButton'; 17 | import ClusterDynamicDetails from '../components/ClusterDynamicDetails'; 18 | import { useNavigate } from 'react-router-dom'; 19 | import orbitalLogo from '../assets/transparent-orbital.png'; 20 | import HeartIcon from '@mui/icons-material/Favorite'; 21 | import TopicIcon from '@mui/icons-material/Article'; 22 | import OverviewIcon from '@mui/icons-material/TravelExplore'; 23 | 24 | const drawerWidth = 100; 25 | 26 | const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })( 27 | ({ theme, open }) => ({ 28 | // flexGrow: 1, 29 | // padding: theme.spacing(3), 30 | transition: theme.transitions.create('margin', { 31 | easing: theme.transitions.easing.sharp, 32 | duration: theme.transitions.duration.leavingScreen, 33 | }), 34 | ...(open && { 35 | transition: theme.transitions.create('margin', { 36 | easing: theme.transitions.easing.easeOut, 37 | duration: theme.transitions.duration.enteringScreen, 38 | }), 39 | backgroundColor: '#484995', 40 | marginLeft: 0, 41 | }), 42 | }) 43 | ); 44 | 45 | const AppBar = styled(MuiAppBar, { 46 | shouldForwardProp: (prop) => prop !== 'open', 47 | })(({ theme, open }) => ({ 48 | transition: theme.transitions.create(['margin', 'width'], { 49 | easing: theme.transitions.easing.sharp, 50 | duration: theme.transitions.duration.leavingScreen, 51 | }), 52 | 53 | backgroundColor: '#000000', 54 | ...(open && { 55 | width: `calc(100% - ${drawerWidth}px)`, 56 | marginLeft: `${drawerWidth}px`, 57 | transition: theme.transitions.create(['margin', 'width'], { 58 | easing: theme.transitions.easing.easeOut, 59 | duration: theme.transitions.duration.enteringScreen, 60 | }), 61 | backgroundColor: '#000000', 62 | }), 63 | })); 64 | 65 | const DrawerHeader = styled('div')(({ theme }) => ({ 66 | display: 'flex', 67 | alignItems: 'center', 68 | padding: theme.spacing(0, 1), 69 | // necessary for content to be below app bar 70 | ...theme.mixins.toolbar, 71 | justifyContent: 'flex-end', 72 | backgroundColor: '#444444', 73 | })); 74 | 75 | export default function PersistentDrawerLeft({ user }) { 76 | const [open, setOpen] = useState(true); 77 | const [intervalId, setIntervalId] = useState(null); 78 | const [metrics, setMetrics] = useState(0); 79 | const [currentTab, setCurrentTab] = useState('overview'); 80 | const navigate = useNavigate(); 81 | 82 | // Get array of clusters based on userID 83 | 84 | return ( 85 | 92 | 93 | 94 | 95 | 101 | {currentTab[0].toUpperCase() + currentTab.slice(1)} 102 | 103 | 104 | 105 | 116 | 123 | 130 | Orbital logo 140 | 149 | {'Orbital'} 150 | 151 | 152 | 153 | 154 | 162 | 168 | setCurrentTab('overview')} 176 | > 177 | 178 | 179 | 185 | Overview 186 | 187 | 188 | 189 | 190 | 196 | setCurrentTab('health')} 204 | > 205 | 206 | 207 | 213 | Health Metrics 214 | 215 | 216 | 217 | 218 | 224 | setCurrentTab('topic')} 232 | > 233 | 234 | 235 | 241 | Topic Metrics 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 |
257 | 262 |
263 |
264 | ); 265 | } 266 | -------------------------------------------------------------------------------- /src/client/pages/Documentation.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Navbar from '../components/Navbar'; 3 | import Doc from '../components/Doc.jsx'; 4 | 5 | const Documentation = () => { 6 | return ( 7 |
8 |
9 | ) 10 | } 11 | 12 | export default Documentation; -------------------------------------------------------------------------------- /src/client/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import HomePageSideBar from '../components/HomePageSideBar'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import CpuMetrics from '../allMetrics/CpuMetrics.jsx'; 5 | import axios from 'axios'; 6 | import ClusterOverview from '../components/ClusterOverview'; 7 | const Home = ({ setUser, user }) => { 8 | const [userId, setUserId] = useState(localStorage.getItem('userId')); 9 | /* 10 | { 11 | email: "email@email.com", 12 | _id: "12345", 13 | clusterArr: [{},{},{}] 14 | } 15 | 16 | { 17 | "_id":{"$oid":"643ab88e519fc34190e1cf5f"}, 18 | "email":"sid", 19 | "clusters":[], 20 | "__v":{"$numberInt":"0"} 21 | } 22 | */ 23 | const nav = useNavigate(); 24 | useEffect(() => { 25 | if (localStorage.getItem('isLoggedIn') !== 'true') { 26 | nav('/login'); 27 | } 28 | }, []); 29 | 30 | // useEffect(() => { 31 | // const data = { 32 | // userId: userId 33 | // } 34 | // const url = '3001/users/getUserInfo' 35 | // axios.post(url, {data}).then(response => setUser(response.data)).catch(e => console.log(e)) 36 | 37 | // }, [user]) 38 | 39 | return ( 40 |
41 | 42 |
43 | ); 44 | }; 45 | 46 | export default Home; 47 | -------------------------------------------------------------------------------- /src/client/pages/LandingPage.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Box } from "@mui/material"; 3 | import Navbar from "../components/Navbar"; 4 | import LandingPageHero from "../components/LandingPageHero"; 5 | import LandingPageDemo from "../components/LandingPageDemo"; 6 | import LandingPageTechStack from '../components/LandingPageTechStack' 7 | import LandingPageTeam from '../components/LandingPageTeam' 8 | const LandingPage = () => { 9 | 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | 19 | export default LandingPage; 20 | -------------------------------------------------------------------------------- /src/client/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Box, 4 | Button, 5 | Card, 6 | CardContent, 7 | TextField, 8 | Typography, 9 | } from '@mui/material'; 10 | import { useNavigate } from 'react-router-dom'; 11 | import axios from 'axios'; 12 | import { useState } from 'react'; 13 | import Background from '../assets/Login-background.jpg'; 14 | import TransparentTextLogo from '../assets/Transparent-Logo.png'; 15 | 16 | const styles = { 17 | root: { 18 | display: 'flex', 19 | flexDirection: 'column', 20 | alignItems: 'center', 21 | justifyContent: 'center', 22 | height: '100vh', 23 | // backgroundImage: `url(${signupbg})`, 24 | backgroundSize: 'cover', 25 | backgroundPosition: 'center', 26 | overflow: 'hidden', 27 | }, 28 | card: { 29 | backgroundColor: 'white', 30 | backdropFilter: 'blur(24px) brightness(125%)', 31 | borderRadius: '8px', 32 | maxWidth: '400px', 33 | width: '100%', 34 | boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.1)', 35 | padding: '16px', 36 | }, 37 | input: { 38 | marginBottom: '16px', 39 | }, 40 | submitButton: { 41 | marginTop: '16px', 42 | marginBottom: '8px', 43 | backgroundColor: '#227BA5', 44 | color: '#ffffff', 45 | '&:hover': { 46 | backgroundColor: '#1D6490', 47 | }, 48 | card: { 49 | backgroundColor: 'blue', 50 | backdropFilter: 'blur(40px) brightness(170%)', 51 | borderRadius: '8px', 52 | maxWidth: '500px', 53 | width: '100%', 54 | boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.1)', 55 | padding: '16px', 56 | display: 'flex', 57 | justifyContent: 'center', 58 | alignItems: 'center', 59 | }, 60 | input: { 61 | marginBottom: '16px', 62 | }, 63 | submitButton: { 64 | marginTop: '16px', 65 | marginBottom: '8px', 66 | backgroundColor: '#227BA5', 67 | color: '#ffffff', 68 | '&:hover': { 69 | backgroundColor: '#1D6490', 70 | }, 71 | }, 72 | signupLink: { 73 | color: '##227BA5', 74 | textDecoration: 'none', 75 | '&:hover': { 76 | textDecoration: 'underline', 77 | }, 78 | cursor: 'pointer', 79 | }, 80 | cursor: 'pointer', 81 | }, 82 | }; 83 | const Login = ({ setUser }) => { 84 | const [email, setEmail] = useState(''); 85 | const [password, setPassword] = useState(''); 86 | const navigate = useNavigate(); 87 | 88 | const loginHandler = () => { 89 | if (!email || !password) { 90 | return; 91 | } 92 | axios 93 | .post('/user/login', { email, password }) 94 | .then((response) => { 95 | console.log(response.data); 96 | localStorage.setItem('isLoggedIn', 'true'); 97 | localStorage.setItem('userId', `${response.data._id}`); 98 | setUser(response.data); 99 | navigate('/home'); 100 | }) 101 | .catch((e) => console.log('oopsie')); 102 | }; 103 | return ( 104 | 113 | 114 | 115 | navigate('/')} 117 | sx={{ 118 | mb: '1px', 119 | textAlign: 'center', 120 | fontSize: '50px', 121 | cursor: 'pointer', 122 | }} 123 | > 124 | Orbital logo 130 | 131 | 132 | 140 | Log in to your account 141 | 142 | setEmail(e.target.value)} 149 | /> 150 | setPassword(e.target.value)} 158 | /> 159 | 168 | 172 | Don't have an account?{' '} 173 | 174 | Sign up 175 | 176 | 177 | 178 | 179 | 180 | ); 181 | }; 182 | 183 | export default Login; 184 | -------------------------------------------------------------------------------- /src/client/pages/Signup.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Button, 4 | Card, 5 | CardContent, 6 | TextField, 7 | Typography, 8 | } from "@mui/material"; 9 | 10 | import axios from "axios"; 11 | import { useState } from "react"; 12 | import { useNavigate } from "react-router-dom"; 13 | import Background from '../assets/Login-background.jpg'; 14 | import TransparentTextLogo from '../assets/Transparent-Logo.png'; 15 | 16 | const styles = { 17 | root: { 18 | display: "flex", 19 | flexDirection: "column", 20 | alignItems: "center", 21 | justifyContent: "center", 22 | height: "100vh", 23 | // backgroundImage: `url(${signupbg})`, 24 | backgroundSize: "cover", 25 | backgroundPosition: "center", 26 | overflow: "hidden", 27 | }, 28 | card: { 29 | backgroundColor: "white", 30 | backdropFilter: "blur(40px) brightness(170%)", 31 | borderRadius: "8px", 32 | maxWidth: "500px", 33 | width: "100%", 34 | boxShadow: "0px 2px 8px rgba(0, 0, 0, 0.1)", 35 | padding: "16px", 36 | display: "flex", 37 | justifyContent: "center", 38 | alignItems: "center" 39 | }, 40 | input: { 41 | marginBottom: "16px", 42 | }, 43 | submitButton: { 44 | marginTop: "16px", 45 | marginBottom: "8px", 46 | backgroundColor: "#227BA5", 47 | color: "#ffffff", 48 | "&:hover": { 49 | backgroundColor: "#1D6490", 50 | }, 51 | }, 52 | signupLink: { 53 | color: "##227BA5", 54 | textDecoration: "none", 55 | "&:hover": { 56 | textDecoration: "underline", 57 | }, 58 | cursor: "pointer", 59 | }, 60 | }; 61 | 62 | const Signup = () => { 63 | const [firstName, setFirstName] = useState(""); 64 | const [lastName, setLastName] = useState(""); 65 | const [email, setEmail] = useState(""); 66 | const [password, setPassword] = useState(""); 67 | const [confirmPass, setConfirmPass] = useState(""); 68 | const [userId, setUserId] = useState(""); 69 | const [phoneNumber, setPhoneNumber] = useState(""); 70 | const navigate = useNavigate(); 71 | 72 | const signUpHandler = () => { 73 | if (!email || !password || !firstName || !lastName) { 74 | return; 75 | } else if (password !== confirmPass) { 76 | return; 77 | } 78 | axios 79 | .post("/user/signup", { email, password, phoneNumber: phoneNumber.replace(/[^0-9]/g, '') }) 80 | .then((response) => { 81 | localStorage.setItem("userId", response.data._id); 82 | localStorage.setItem("isLoggedIn", "true"); 83 | navigate("/home"); 84 | }) 85 | .catch((e) => console.log("oopsie")); 86 | }; 87 | return ( 88 | 95 | 96 | 97 | 98 | navigate('/')} 100 | sx={{ mb: "1px", textAlign: "center", fontSize:"50px", cursor:"pointer" }}> 101 | Orbital logo 102 | 103 | 111 | Sign Up 112 | 113 | setFirstName(e.target.value)} 121 | /> 122 | setLastName(e.target.value)} 130 | /> 131 | setEmail(e.target.value)} 138 | /> 139 | setPassword(e.target.value)} 147 | /> 148 | setConfirmPass(e.target.value)} 156 | /> 157 | setPhoneNumber(e.target.value)} 165 | /> 166 | 176 | 180 | Already have an account?{" "} 181 | 182 | Log In 183 | 184 | 185 | 186 | 187 | 188 | ); 189 | }; 190 | 191 | export default Signup; 192 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | box-sizing: border-box; 5 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { BrowserRouter } from "react-router-dom"; 4 | import App from "./App"; 5 | import "./index.css"; 6 | const root = ReactDOM.createRoot(document.getElementById("root")); 7 | root.render( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/server/controllers/alertController.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const Alert = require('../models/alertModel'); 3 | const User = require('../models/userModel'); 4 | const ZAPIER_HOOK_URL = ''; // Enter your URL here 5 | 6 | const alertController = { 7 | async getUserAlerts(req, res, next) { 8 | const { id } = req.params; 9 | 10 | try { 11 | const alerts = await Alert.find({ owner: id }); 12 | res.locals.alerts = alerts; 13 | return next(); 14 | } catch (e) { 15 | return next(e); 16 | } 17 | }, 18 | async createAlert(req, res, next) { 19 | console.log('creating'); 20 | const { id, metric, over, under } = req.body; 21 | const alert = new Alert({ 22 | metric, 23 | over, 24 | under, 25 | owner: id, 26 | }); 27 | try { 28 | await alert.save(); 29 | } catch (e) { 30 | return next(e); 31 | } 32 | return next(); 33 | }, 34 | 35 | async deleteAlert(req, res, next) { 36 | const { id } = req.params; 37 | 38 | try { 39 | res.locals.deleted = await Alert.findOneAndDelete({ _id: id }); 40 | return next(); 41 | } catch (error) { 42 | return next(error); 43 | } 44 | }, 45 | 46 | async sendAlert(req, res, next) { 47 | const { phoneNumber, message } = req.body; 48 | await axios.post('https://hooks.zapier.com/hooks/catch/15123291/3uedfdp/', { 49 | phoneNumber, 50 | message, 51 | }); 52 | return next(); 53 | }, 54 | async checkRanges(req, res, next) { 55 | const { userId } = req.body; 56 | const metrics = res.locals.metric; 57 | 58 | let outOfRangeMessage = ''; 59 | 60 | try { 61 | // Fetch the user object using the user ID 62 | const user = await User.findById(userId); 63 | if (!user.phoneNumber) { 64 | return next(); 65 | } else { 66 | // Fetch the alerts associated with the user 67 | const userAlerts = await Alert.find({ owner: userId }); 68 | 69 | // Iterate through the user alerts and check if any metric is out of range 70 | for (const alert of userAlerts) { 71 | if (alert.lastSent > Date.now() - 60000 * 120) { 72 | continue; 73 | } 74 | const metricKey = alert.metric; 75 | const metricValue = metrics[metricKey]; 76 | if (!metricValue) { 77 | continue; 78 | } 79 | if (metricValue < alert.under || metricValue > alert.over) { 80 | outOfRangeMessage += `Metric "${metricKey}" is out of range with a value of ${metricValue}. `; 81 | } 82 | } 83 | if (outOfRangeMessage) { 84 | res.locals.outOfRangeMessage = outOfRangeMessage; 85 | const phoneNumber = user.phoneNumber; 86 | await axios.post(ZAPIER_HOOK_URL, { 87 | phoneNumber: phoneNumber, 88 | message: outOfRangeMessage, 89 | }); 90 | } 91 | next(); 92 | } 93 | } catch (error) { 94 | next(error); 95 | } 96 | }, 97 | }; 98 | 99 | module.exports = alertController; 100 | -------------------------------------------------------------------------------- /src/server/controllers/clusterController.js: -------------------------------------------------------------------------------- 1 | const Cluster = require('../models/clusterModel.js'); 2 | const User = require('../models/userModel.js'); 3 | 4 | const clusterController = { 5 | async createCluster(req, res, next) { 6 | const { cluster_name, prom_port, owner } = req.body; 7 | try { 8 | const newCluster = await Cluster.create({ 9 | name: cluster_name, 10 | prometheusUrl: prom_port, 11 | owner: owner, 12 | }); 13 | res.locals.newcluster = newCluster; 14 | return next(); 15 | } catch (error) { 16 | console.log(error); 17 | return next(error); 18 | } 19 | }, 20 | 21 | async saveClusterToUser(req, res, next) { 22 | const newCluster = res.locals.newcluster; 23 | try { 24 | const user = await User.findOneAndUpdate( 25 | { _id: newCluster.owner }, 26 | { $push: { clusters: newCluster } }, 27 | { new: true } 28 | ); 29 | res.locals.savedcluster = user; 30 | return next(); 31 | } catch (error) { 32 | console.log(error); 33 | return next(error); 34 | } 35 | }, 36 | async updateCluster(req, res, next) { 37 | try { 38 | const { cluster_id, cluster_name, prom_port } = req.body; 39 | 40 | const cluster = await Cluster.findOneAndUpdate( 41 | { _id: cluster_id }, 42 | { name: cluster_name, prometheusUrl: prom_port }, 43 | { new: true } 44 | ); 45 | 46 | res.locals.cluster = cluster; 47 | return next(); 48 | } catch (error) { 49 | console.log(error); 50 | return next(error); 51 | } 52 | }, 53 | async deleteCluster(req, res, next) { 54 | try { 55 | const id = req.params.id; 56 | const deletedCluster = await Cluster.findByIdAndDelete(id); 57 | if (!deletedCluster) { 58 | return res.status(404).send({ message: "Cluster not found" }); 59 | } 60 | res.locals.deletedCluster = deletedCluster; 61 | return next(); 62 | } catch (error) { 63 | error.message = 64 | "Error in clusterController.deleteCluster middleware."; 65 | return next(error); 66 | } 67 | }, 68 | async getUserClusters(req, res, next) { 69 | try { 70 | const id = req.params.id; 71 | const clusterArr = await Cluster.find({ owner: id }); 72 | res.locals.clusterArr = clusterArr; 73 | return next(); 74 | } catch (err) { 75 | err.message = 76 | "Error in ClusterController.getUserClusters middleware."; 77 | return next(err); 78 | } 79 | }, 80 | async getClusterById(req, res, next) { 81 | try { 82 | const id = req.params.id; 83 | res.locals.cluster = await Cluster.findOne({ _id: id }); 84 | return next(); 85 | } catch (err) { 86 | err.message = 87 | "Error in ClusterController.getClusterById middleware."; 88 | return next(err); 89 | } 90 | }, 91 | }; 92 | 93 | module.exports = clusterController; -------------------------------------------------------------------------------- /src/server/controllers/kafkaController.js: -------------------------------------------------------------------------------- 1 | const { Kafka } = require('kafkajs'); 2 | 3 | const KafkaController = { 4 | async getMetrics(req, res, next) { 5 | try { 6 | const { brokers } = req.body; 7 | const kafka = new Kafka({ 8 | clientId: 'my-app', 9 | brokers, 10 | }); 11 | const admin = kafka.admin(); 12 | await admin.connect(); 13 | const topics = await admin.listTopics(); 14 | const offsets = await admin.fetchTopicOffsets(topics[4]); 15 | const consumerGroups = await admin.fetchOffsets({ 16 | groupId: 'my-app', 17 | topics: [topics[4]], 18 | }); 19 | const describedCluster = await admin.describeCluster(); 20 | res.locals.metrics = { 21 | topics, 22 | offsets, 23 | consumerGroups, 24 | describedCluster, 25 | }; 26 | await admin.disconnect(); 27 | return next(); 28 | } catch (e) { 29 | return next(e); 30 | } 31 | }, 32 | }; 33 | 34 | module.exports = KafkaController; -------------------------------------------------------------------------------- /src/server/controllers/metricsController.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | // const cpuQuery = 4 | // 'http://localhost:9090/api/v1/query?query=sum(rate(process_cpu_seconds_total[1m])) * 100'; 5 | 6 | const metricsController = { 7 | async getCoreMetrics(req, res, next) { 8 | const { broker } = req.body; 9 | try { 10 | const cpuMetric = await axios.get( 11 | `http://${broker}/api/v1/query?query=sum(rate(process_cpu_seconds_total[1m])) * 100` 12 | ); 13 | const bytesintotalmetric = await axios.get( 14 | `http://${broker}/api/v1/query?query=sum(rate(kafka_server_brokertopicmetrics_bytesin_total[1m]))` 15 | ); 16 | const bytesOutMetric = await axios.get( 17 | `http://${broker}/api/v1/query?query=sum(rate(kafka_server_brokertopicmetrics_bytesout_total[1m]))` 18 | ); 19 | const ramUsage = await axios.get( 20 | `http://${broker}/api/v1/query?query=sum(rate(process_resident_memory_bytes[1m]))` 21 | ); 22 | const latency = await axios.get( 23 | `http://${broker}/api/v1/query?query=sum(rate(kafka_network_requestmetrics_totaltimems{}[1m]) - rate(kafka_network_requestmetrics_localtimems{}[1m]))` 24 | ); 25 | res.locals.metric = { 26 | cpumetric: cpuMetric.data.data.result[0].value[1], 27 | bytesintotalmetric: bytesintotalmetric.data.data.result[0].value[1], 28 | bytesOutMetric: bytesOutMetric.data.data.result[0].value[1], 29 | ramUsageMetric: ramUsage.data.data.result[0].value[1], 30 | latency: latency.data.data.result[0].value[1], 31 | }; 32 | return next(); 33 | } catch (error) { 34 | console.log(error, 'Error in getMetrics'); 35 | return next(); 36 | } 37 | }, 38 | 39 | 40 | async getAllTopics(req, res, next) { 41 | try { 42 | const { broker } = req.body; 43 | const topicMetrics = await axios.get( 44 | `http://${broker}/api/v1/query?query=count(kafka_topic_partition_current_offset) by (topic)` 45 | ); 46 | res.locals.topics = topicMetrics.data; 47 | return next(); 48 | } catch (e) { 49 | return next(e); 50 | } 51 | }, 52 | 53 | async getTopicMetrics(req, res, next) { 54 | try { 55 | const { topic, broker } = req.body; 56 | const topicMetrics = await axios.get( 57 | `http://${broker}/api/v1/query?query=kafka_topic_partition_current_offset{topic="${topic}"}` 58 | ); 59 | res.locals.metric = topicMetrics.data; 60 | return next(); 61 | } catch (e) { 62 | e.message = 'Error in getTopicMetrics controller'; 63 | return next(e); 64 | } 65 | }, 66 | 67 | async getProducerConsumerMetrics(req, res, next) { 68 | try { 69 | const { broker } = req.body; 70 | const producerRequestsTotal = await axios.get( 71 | `http://${broker}/api/v1/query?query=rate(kafka_server_brokertopicmetrics_totalproducerequests_total[1m])` 72 | ); 73 | const producersMessagesInTotal = await axios.get( 74 | `http://${broker}/api/v1/query?query=rate(kafka_server_brokertopicmetrics_messagesin_total[1m])` 75 | ); 76 | const producerConversionsTotal = await axios.get( 77 | `http://${broker}/api/v1/query?query=rate(kafka_server_brokertopicmetrics_producemessageconversions_total[1m])` 78 | ); 79 | const consumerRequestsTotal = await axios.get( 80 | `http://${broker}/api/v1/query?query=rate(kafka_server_brokertopicmetrics_totalfetchrequests_total[1m])` 81 | ); 82 | const consumerFailedRequestsTotal = await axios.get( 83 | `http://${broker}/api/v1/query?query=rate(kafka_server_brokertopicmetrics_failedfetchrequests_total[1m])` 84 | ); 85 | const consumerConversionsTotal = await axios.get( 86 | `http://${broker}/api/v1/query?query=rate(kafka_server_brokertopicmetrics_fetchmessageconversions_total[1m])` 87 | ); 88 | res.locals.metric = { 89 | producerConversionsTotal: 90 | producerConversionsTotal.data.data.result[0].value[1], 91 | producerRequestsTotal: 92 | producerRequestsTotal.data.data.result[0].value[1], 93 | producersMessagesInTotal: 94 | producersMessagesInTotal.data.data.result[0].value[1], 95 | consumerConversionsTotal: 96 | consumerConversionsTotal.data.data.result[0].value[1], 97 | consumerFailedRequestsTotal: 98 | consumerFailedRequestsTotal.data.data.result[0].value[1], 99 | consumerRequestsTotal: 100 | consumerRequestsTotal.data.data.result[0].value[1], 101 | }; 102 | return next(); 103 | } catch (e) { 104 | e.message = 'Error in getProducerMetricsController'; 105 | return next(e); 106 | } 107 | }, 108 | 109 | async getProducerConsumerMetrics(req, res, next) { 110 | try { 111 | const { broker } = req.body; 112 | const producerRequestsTotal = await axios.get( 113 | `http://${broker}/api/v1/query?query=rate(kafka_server_brokertopicmetrics_totalproducerequests_total[1m])` 114 | ); 115 | const producersMessagesInTotal = await axios.get( 116 | `http://${broker}/api/v1/query?query=rate(kafka_server_brokertopicmetrics_messagesin_total[1m])` 117 | ); 118 | const producerConversionsTotal = await axios.get( 119 | `http://${broker}/api/v1/query?query=rate(kafka_server_brokertopicmetrics_producemessageconversions_total[1m])` 120 | ); 121 | const consumerRequestsTotal = await axios.get( 122 | `http://${broker}/api/v1/query?query=rate(kafka_server_brokertopicmetrics_totalfetchrequests_total[1m])` 123 | ); 124 | const consumerFailedRequestsTotal = await axios.get( 125 | `http://${broker}/api/v1/query?query=rate(kafka_server_brokertopicmetrics_failedfetchrequests_total[1m])` 126 | ); 127 | const consumerConversionsTotal = await axios.get( 128 | `http://${broker}/api/v1/query?query=rate(kafka_server_brokertopicmetrics_fetchmessageconversions_total[1m])` 129 | ); 130 | res.locals.metric = { 131 | producerConversionsTotal: 132 | producerConversionsTotal.data.data.result[0].value[1], 133 | producerRequestsTotal: 134 | producerRequestsTotal.data.data.result[0].value[1], 135 | producersMessagesInTotal: 136 | producersMessagesInTotal.data.data.result[0].value[1], 137 | consumerConversionsTotal: 138 | consumerConversionsTotal.data.data.result[0].value[1], 139 | consumerFailedRequestsTotal: 140 | consumerFailedRequestsTotal.data.data.result[0].value[1], 141 | consumerRequestsTotal: 142 | consumerRequestsTotal.data.data.result[0].value[1], 143 | }; 144 | return next(); 145 | } catch (e) { 146 | e.message = 'Error in getProducerMetricsController'; 147 | return next(e); 148 | } 149 | }, 150 | }; 151 | 152 | module.exports = metricsController; -------------------------------------------------------------------------------- /src/server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/userModel.js"); 2 | 3 | const UserController = { 4 | async createUser(req, res, next) { 5 | try { 6 | const { email, password, phoneNumber } = req.body; 7 | const user = new User({ email: email, password: password, phoneNumber: phoneNumber }); 8 | await user.save(); 9 | res.locals.account_created = user; 10 | return next(); 11 | } catch (err) { 12 | err.message = "Error in UserController.createUser middleware."; 13 | return next(err); 14 | } 15 | }, 16 | 17 | verifyUser(req, res, next) { 18 | const { email, password } = req.body; 19 | console.log(email, password) 20 | User.findOne({ email: email }) 21 | .then((user) => { 22 | console.log('user: ', user) 23 | if (!user) { 24 | console.log('no user found') 25 | res.locals.user = false; 26 | return next(); 27 | } else { 28 | console.log('user found') 29 | user.comparePassword(password, (err, isMatch) => { 30 | if (err) return next(err) 31 | delete user.password; 32 | res.locals.user = user; 33 | console.log("user is verified"); 34 | return next(); 35 | }); 36 | } 37 | }) 38 | .catch((err) => { 39 | return next(err); 40 | }); 41 | }, 42 | getUser(req, res, next) { 43 | const { id } = req.params; 44 | console.log({ id }); 45 | User.findOne({ _id: id }) 46 | .then((data) => { 47 | delete data.password 48 | res.locals.user = data; 49 | next(); 50 | }) 51 | .catch((err) => { 52 | return next(err); 53 | }); 54 | }, 55 | }; 56 | 57 | module.exports = UserController; -------------------------------------------------------------------------------- /src/server/models/alertModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const AlertSchema = new Schema( 5 | { 6 | metric: { type: String, required: true }, 7 | over: { type: Number, required: true }, 8 | under: { type: Number, required: true }, 9 | lastSent: {type: Date, default: Date.now() - (60000 * 120)}, 10 | owner: { type: Schema.Types.ObjectId, required: true, ref: 'User' }, 11 | }, 12 | { collection: 'alerts' } 13 | ); 14 | 15 | const Alert = mongoose.model('Alert', AlertSchema); 16 | module.exports = Alert; 17 | -------------------------------------------------------------------------------- /src/server/models/clusterModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const ClusterSchema = new Schema( 5 | { 6 | name: { type: String, required: true }, 7 | prometheusUrl: { type: String, required: true }, 8 | owner: { type: Schema.Types.ObjectId, required: true, ref: "User" }, 9 | }, 10 | { collection: "clusters" } 11 | ); 12 | 13 | const Cluster = mongoose.model("Cluster", ClusterSchema); 14 | module.exports = Cluster; 15 | -------------------------------------------------------------------------------- /src/server/models/userModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | const bcrypt = require("bcrypt"); 4 | 5 | const UserSchema = new Schema( 6 | { 7 | email: { type: String, required: true, unique: true }, 8 | password: { type: String, required: true }, 9 | phoneNumber: { type: String, required: false }, 10 | }, 11 | { collection: "users" } 12 | ); 13 | 14 | UserSchema.pre("save", function (next) { 15 | const user = this; 16 | bcrypt.genSalt(10, function (saltError, salt) { 17 | if (saltError) { 18 | return next(saltError); 19 | } else { 20 | bcrypt.hash(user.password, salt, function (hashError, hash) { 21 | if (hashError) { 22 | return next(hashError); 23 | } else { 24 | user.password = hash; 25 | return next(); 26 | } 27 | }); 28 | } 29 | }); 30 | }); 31 | 32 | UserSchema.methods.comparePassword = function (enteredPassword, callback) { 33 | bcrypt.compare(enteredPassword, this.password, (err, isMatch) => { 34 | if (err) { 35 | return callback(err); 36 | } 37 | callback(null, isMatch); 38 | }); 39 | }; 40 | 41 | const User = mongoose.model("User", UserSchema); 42 | module.exports = User; 43 | -------------------------------------------------------------------------------- /src/server/routes/alertRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const alertController = require('../controllers/alertController.js'); 3 | 4 | const router = express.Router(); 5 | 6 | router.post('/', alertController.createAlert, (req, res) => { 7 | res.sendStatus(200); 8 | }); 9 | 10 | router.get('/:id', alertController.getUserAlerts, (req, res) => { 11 | res.json(res.locals.alerts); 12 | }) 13 | 14 | router.delete('/:id', alertController.deleteAlert, (req, res) => { 15 | res.json(res.locals.deleted || "Hello"); 16 | }) 17 | 18 | module.exports = router; -------------------------------------------------------------------------------- /src/server/routes/clusterRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const clusterController = require('../controllers/clusterController.js'); 3 | 4 | const router = express.Router(); 5 | 6 | router.get('/:id', clusterController.getUserClusters, (req, res) => { 7 | res.json(res.locals.clusterArr); 8 | }); 9 | 10 | 11 | router.get('/clusterById/:id', clusterController.getClusterById, (req, res) => { 12 | res.json(res.locals.cluster); 13 | }); 14 | 15 | router.post('/', clusterController.createCluster, (req, res) => { 16 | res.status(200).json(res.locals.savedcluster); 17 | }); 18 | 19 | router.patch('/', clusterController.updateCluster, (req, res) => { 20 | res.status(200).json(res.locals.cluster); 21 | }); 22 | 23 | router.delete('/:id', clusterController.deleteCluster, (req, res) => { 24 | res.status(200).json(res.locals.deletedCluster); 25 | }); 26 | 27 | module.exports = router; -------------------------------------------------------------------------------- /src/server/routes/kafkaRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const KafkaController = require("../controllers/kafkaController.js"); 3 | 4 | const router = express.Router(); 5 | 6 | router.post('/metrics', KafkaController.getMetrics, (req, res) => { 7 | res.status(200).json(res.locals.metrics); 8 | }) 9 | 10 | module.exports = router; -------------------------------------------------------------------------------- /src/server/routes/metricsRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const metricsController = require('../controllers/metricsController.js'); 3 | const router = express.Router(); 4 | const alertController = require('../controllers/alertController.js'); 5 | 6 | 7 | // POST request to metrics endpoint 8 | router.post('/metrics', metricsController.getCoreMetrics, (req, res) => { 9 | 10 | res.status(200).json(res.locals.metric); 11 | }); 12 | 13 | router.post('/topics', metricsController.getAllTopics, (req, res) => { 14 | res.status(200).json(res.locals.topics); 15 | }); 16 | 17 | router.post('/topicMetrics', metricsController.getTopicMetrics, (req, res) => { 18 | res.status(200).json(res.locals.metric); 19 | }); 20 | 21 | router.post( 22 | '/producerConsumerMetrics', 23 | metricsController.getProducerConsumerMetrics, 24 | (req, res) => { 25 | res.status(200).json(res.locals.metric); 26 | } 27 | ); 28 | 29 | module.exports = router; 30 | -------------------------------------------------------------------------------- /src/server/routes/userRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const userController = require("../controllers/userController.js"); 3 | 4 | const router = express.Router(); 5 | 6 | // POST request to login endpoint to verify current user 7 | router.post("/login", userController.verifyUser, (req, res) => { 8 | res.json(res.locals.user); 9 | }); 10 | 11 | // POST request to signup endpoint to create new user 12 | router.post("/signup", userController.createUser, (req, res) => { 13 | res.json(res.locals.account_created); 14 | }); 15 | 16 | // GET request to get user ID 17 | router.get("/:id", userController.getUser, (req, res) => { 18 | res.json(res.locals.user); 19 | }); 20 | 21 | module.exports = router; -------------------------------------------------------------------------------- /src/server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const mongoose = require('mongoose'); 4 | const userRouter = require('./routes/userRouter.js'); 5 | const kafkaRouter = require('./routes/kafkaRouter.js'); 6 | const metricsRouter = require('./routes/metricsRouter.js'); 7 | const clusterRouter = require('./routes/clusterRouter.js'); 8 | const alertRouter = require('./routes/alertRouter.js'); 9 | 10 | const cors = require('cors'); 11 | const app = express(); 12 | 13 | const PORT = process.env.PORT || 3001; 14 | 15 | const URI = 16 | 'mongodb+srv://cowboysintvland:mypassword11@cluster0.kniri3w.mongodb.net/test'; 17 | 18 | mongoose.connect(URI, { useNewUrlParser: true, useUnifiedTopology: true }); 19 | mongoose.connection.once('open', () => { 20 | console.log('Connected to Database'); 21 | }); 22 | 23 | app.use(cors()); 24 | app.use(express.json()); 25 | app.use(express.urlencoded({ extended: true })); 26 | app.use('/user', userRouter); 27 | // app.use("/kafka", kafkaRouter); 28 | app.use('/jmx', metricsRouter); 29 | app.use('/cluster', clusterRouter); 30 | app.use('/alerts', alertRouter); 31 | 32 | // Serve static files from the React build directory 33 | app.use(express.static(path.join(__dirname, '..', '..', 'build'))); 34 | 35 | app.get('*', (req, res) => { 36 | res.sendFile(path.resolve(__dirname, '..', '..', 'build', 'index.html')); 37 | }); 38 | 39 | /* TESTING POST REQ ROUTE */ 40 | app.post('/test', (req, res) => { 41 | res.json(req.body); 42 | }); 43 | 44 | // global err handler: 45 | app.use((err, req, res, next) => { 46 | console.error(err.stack); 47 | res.status(500).json({ 48 | status: 'error', 49 | message: 'Something went wrong', 50 | error: err.message, 51 | }); 52 | }); 53 | 54 | //Unknown Route Handler 55 | app.use((req, res) => res.sendStatus(404)); 56 | 57 | app.listen(PORT, () => { 58 | console.log(`Server is listening on port ${PORT}`); 59 | }); 60 | 61 | module.exports = app; 62 | --------------------------------------------------------------------------------