├── .DS_Store ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── client ├── App.jsx ├── Launch_Material │ ├── branding │ │ ├── centered_neptune.png │ │ ├── intro neptune.png │ │ ├── intro_nept.png │ │ └── introducing.png │ ├── logo │ │ ├── neptune.svg │ │ ├── neptune2.png │ │ └── neptune_logo.png │ └── team_images │ │ ├── JinYoo.png │ │ ├── LawrenceYeh.png │ │ ├── Miranda-Headshot2022 circle.png │ │ ├── Miranda-Headshot2022 square.jpeg │ │ ├── Miranda-Headshot2022.jpg │ │ └── SwanHtet.png ├── components │ ├── AreaChart.jsx │ ├── BasicCard.jsx │ ├── Depl.jsx │ ├── DoughnutChart.jsx │ ├── GaugeChartTemplate.jsx │ ├── LineChart.jsx │ ├── Namespace.jsx │ ├── Node.jsx │ ├── NotificationCard.jsx │ ├── PieChart.jsx │ ├── Pods.jsx │ ├── PolarAreaChart.jsx │ ├── SearchBar.jsx │ ├── Service.jsx │ └── VerticalLineChart.jsx ├── containers │ ├── DetailsContainer.jsx │ ├── HomeContainer.jsx │ ├── MiniDrawer.jsx │ ├── NotificationContainer.jsx │ └── QueryContainer.jsx ├── index.js └── styles │ └── style.css ├── index.html ├── package-lock.json ├── package.json ├── server ├── controllers │ ├── k8sClientController.js │ ├── promController.js │ └── queryController.js ├── routes │ └── routes.js └── server.js └── webpack.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Neptune/6c5a31e0d5a27f67315a5f8a8e10e969c2c809da/.DS_Store -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | __tests__ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | WORKDIR /usr/src/app 3 | RUN npm install -g webpack nodemon 4 | COPY . /usr/src/app 5 | RUN npm install 6 | RUN npm run build 7 | EXPOSE 3000 8 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introducing Neptune 2 |

3 | neptune  4 |

5 |

6 | Light-weight, simple, and straightforward learning tool for your Kubernetes cluster 7 | 8 | Neptune is an approachable learning tool, light-weight in nature with a few monitoring features. PromQL has a steep learning curve and not everyone needs to learn how to use these queries. We have pre-selected a few relevant queries that display basic data about a simple Kubernetes cluster. This is to reduce the amount of noise when engaging with Prometheus. Neptune is meant to be an easy, visual way to understand a K8s cluster. 9 | Learn more about Neptune here. 10 | 11 | ## Focus on what matters, with built in alerts and health monitoring. 12 | 13 | Render the metrics of your nodes, pods, and namespaces all in one easy to visualize UI. 14 | 15 | ![neptune1](https://user-images.githubusercontent.com/96557317/180123940-4a9f50bc-a63e-45fd-904a-d3b346686138.gif) 16 | 17 | ## Metrics that matter 18 | 19 | Rather than being overloaded with countless metrics, focus on the ones that matter. We highlighted prominent data points related to CPU, Memory, and Network Usage 20 | 21 | ![neptune2](https://user-images.githubusercontent.com/96557317/180123960-78356a62-17bd-495b-bac9-179a34449796.gif) 22 | 23 | ## Alerts, Event Logs, and Cluster Health 24 | 25 | Sort through your firing alerts by severity to stay on top of your alerts 26 | Quick and simplified overview of your cluster health and performance 27 | 28 | ![neptune3](https://user-images.githubusercontent.com/96557317/180123967-93d53f8c-aed1-4e67-8d96-963554b87f63.gif) 29 | 30 | # Getting Started 31 | 32 | Prerequisites 33 | There are some prerequisites before you start with Neptune, so make sure you have Docker and Minikube setup. We highly recommend that you follow the order below. 34 | 35 | Install Docker Desktop - the fastest way to containerize applications 36 | Install minikube - minikube quickly sets up a local Kubernetes cluster on macOS, Linux, and Windows 37 | Install Helm - package manager for Kubernetes 38 | Deploy Prometheus - software application that scrapes clusters and returns metrics 39 | 40 | Note: Make sure your minikube cluster is actively running! 41 | 42 | 1. Clone this repo and then change directory into the root folder. Then run the command below: 43 | git clone git@github.com:oslabs-beta/Neptune.git 44 | 45 | 2. Install dependencies 46 | Run these commands from within the root directory: 47 | 48 | npm install 49 | 50 | npm run build 51 | 52 | npm run start 53 | 54 | 3. Port-forward Prometheus to 9090 55 | To port-forward Prometheus, run the following command. Remember that your minikube cluster needs to be actively running! 56 | 57 | kubectl port-forward prometheus-prometheus-kube-prometheus-prometheus-0 --namespace=default 9090:9090 58 | 59 | 4. Explore Neptune! 60 | Go to http://localhost:3000/ and start exploring your Kubernetes cluster! 61 | 62 | ## 🛠   Built With : 63 | 64 |

65 | Kubernetes  66 | React  67 | Prometheus  68 | Material UI  69 | CSS  70 | HTML  71 | JavaScript  72 | Webpack  73 | NodeJS  74 | Postman  75 |   76 |   77 | 78 |

79 | 80 | 81 | 82 | 83 | ## The Core Team 84 | 85 | 94 | 103 | 112 | 121 |
86 | 87 |
88 | Swan Htet 89 |
90 | GitHub 91 |
92 | LinkedIn 93 |
95 | 96 |
97 | Miranda Jaramillo 98 |
99 | GitHub 100 |
101 | LinkedIn 102 |
104 | 105 |
106 | Lawrence Yeh 107 |
108 | GitHub 109 |
110 | LinkedIn 111 |
113 | 114 |
115 | Jin Yoo 116 |
117 | GitHub 118 |
119 | LinkedIn 120 |
122 | -------------------------------------------------------------------------------- /client/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 3 | import DetailsContainer from './containers/DetailsContainer'; 4 | import MiniDrawer from './containers/MiniDrawer'; 5 | import { Container, Grid } from '@mui/material'; 6 | import NotificationContainer from './containers/NotificationContainer'; 7 | import QueryContainer from './containers/QueryContainer'; 8 | import HomeContainer from './containers/HomeContainer'; 9 | import '../client/styles/style.css'; 10 | 11 | 12 | 13 | 14 | function App() { 15 | return ( 16 | <> 17 |
18 | {/* 19 |
20 | 21 | 22 | 23 | 24 | */} 30 | 31 | 32 | 33 | 34 | } /> 35 | } /> 36 | } /> 37 | 38 | 39 | 40 | {/* 41 | 42 |
43 | */} 44 |
45 | 46 | ); 47 | } 48 | 49 | export default App; 50 | -------------------------------------------------------------------------------- /client/Launch_Material/branding/centered_neptune.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Neptune/6c5a31e0d5a27f67315a5f8a8e10e969c2c809da/client/Launch_Material/branding/centered_neptune.png -------------------------------------------------------------------------------- /client/Launch_Material/branding/intro neptune.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Neptune/6c5a31e0d5a27f67315a5f8a8e10e969c2c809da/client/Launch_Material/branding/intro neptune.png -------------------------------------------------------------------------------- /client/Launch_Material/branding/intro_nept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Neptune/6c5a31e0d5a27f67315a5f8a8e10e969c2c809da/client/Launch_Material/branding/intro_nept.png -------------------------------------------------------------------------------- /client/Launch_Material/branding/introducing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Neptune/6c5a31e0d5a27f67315a5f8a8e10e969c2c809da/client/Launch_Material/branding/introducing.png -------------------------------------------------------------------------------- /client/Launch_Material/logo/neptune.svg: -------------------------------------------------------------------------------- 1 | neptune -------------------------------------------------------------------------------- /client/Launch_Material/logo/neptune2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Neptune/6c5a31e0d5a27f67315a5f8a8e10e969c2c809da/client/Launch_Material/logo/neptune2.png -------------------------------------------------------------------------------- /client/Launch_Material/logo/neptune_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Neptune/6c5a31e0d5a27f67315a5f8a8e10e969c2c809da/client/Launch_Material/logo/neptune_logo.png -------------------------------------------------------------------------------- /client/Launch_Material/team_images/JinYoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Neptune/6c5a31e0d5a27f67315a5f8a8e10e969c2c809da/client/Launch_Material/team_images/JinYoo.png -------------------------------------------------------------------------------- /client/Launch_Material/team_images/LawrenceYeh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Neptune/6c5a31e0d5a27f67315a5f8a8e10e969c2c809da/client/Launch_Material/team_images/LawrenceYeh.png -------------------------------------------------------------------------------- /client/Launch_Material/team_images/Miranda-Headshot2022 circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Neptune/6c5a31e0d5a27f67315a5f8a8e10e969c2c809da/client/Launch_Material/team_images/Miranda-Headshot2022 circle.png -------------------------------------------------------------------------------- /client/Launch_Material/team_images/Miranda-Headshot2022 square.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Neptune/6c5a31e0d5a27f67315a5f8a8e10e969c2c809da/client/Launch_Material/team_images/Miranda-Headshot2022 square.jpeg -------------------------------------------------------------------------------- /client/Launch_Material/team_images/Miranda-Headshot2022.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Neptune/6c5a31e0d5a27f67315a5f8a8e10e969c2c809da/client/Launch_Material/team_images/Miranda-Headshot2022.jpg -------------------------------------------------------------------------------- /client/Launch_Material/team_images/SwanHtet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Neptune/6c5a31e0d5a27f67315a5f8a8e10e969c2c809da/client/Launch_Material/team_images/SwanHtet.png -------------------------------------------------------------------------------- /client/components/AreaChart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | PointElement, 7 | LineElement, 8 | Title, 9 | Tooltip, 10 | Filler, 11 | Legend, 12 | } from 'chart.js'; 13 | import { Line } from 'react-chartjs-2'; 14 | 15 | ChartJS.register( 16 | CategoryScale, 17 | LinearScale, 18 | PointElement, 19 | LineElement, 20 | Title, 21 | Tooltip, 22 | Filler, 23 | Legend 24 | ); 25 | 26 | export const options = { 27 | responsive: true, 28 | plugins: { 29 | legend: { 30 | position: 'top', 31 | }, 32 | title: { 33 | display: true, 34 | text: 'Network traffic', 35 | }, 36 | }, 37 | }; 38 | 39 | 40 | const labels = ['0.00 ms', '500.00 ms', '1000.00 ms', '1500.00 ms', '2000.00 ms', '2500.00 ms', '3000.00 ms']; 41 | const dummyData = [20, 262, 175, 210, 324, 149, 120]; 42 | 43 | export const data = { 44 | labels, 45 | datasets: [ 46 | { 47 | fill: true, 48 | label: 'Dataset 2', 49 | data: dummyData, 50 | borderColor: 'rgb(53, 162, 235)', 51 | backgroundColor: 'rgba(53, 162, 235, 0.5)', 52 | }, 53 | ], 54 | }; 55 | 56 | export default function AreaChart({networkData, networkType}) { 57 | 58 | // iterate thru the object, skip the undefined (first element) 59 | 60 | //Access the array of each of the key 61 | //each array will contain an array of y values 62 | // x - time (30 min) -- labels / and y - values are data 63 | 64 | // the will be in byte, divide by a million to make in mega bytes 65 | 66 | 67 | const data2 = {}; 68 | data2.labels = []; 69 | let minCount = 0; 70 | console.log('Network data inside Area Chart component', networkData); 71 | for(let i = 0; i < networkData['undefined'].length; i++ ) //the count of x-values depend on the length of the array of each key 72 | { 73 | data2.labels.push(`${minCount = minCount + 30}min`); 74 | } 75 | // for each dataset we will have an obj 76 | data2.datasets = []; 77 | 78 | 79 | const clusterNetWork = {}; 80 | clusterNetWork.fill = true; 81 | clusterNetWork.label = networkType; 82 | clusterNetWork.data = []; 83 | const outerArr = networkData["undefined"] //outer array contains array of mem usage 84 | outerArr.forEach( innerArr => {clusterNetWork.data.push(innerArr[0] % 1000)}); //take the first value of each inner array 85 | //random colors from Details Container 86 | const r = Math.floor(Math.random() * 255); 87 | const g = Math.floor(Math.random() * 255); 88 | const b = Math.floor(Math.random() * 255); 89 | clusterNetWork.borderColor = `rgba(${r},${g},${b})`; 90 | clusterNetWork.backgroundColor = `rgba(${r},${g},${b}, 0.5)`; 91 | 92 | data2.datasets.push(clusterNetWork); 93 | 94 | 95 | 96 | 97 | return ; 98 | } 99 | -------------------------------------------------------------------------------- /client/components/BasicCard.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Box from '@mui/material/Box'; 3 | import Card from '@mui/material/Card'; 4 | import CardActions from '@mui/material/CardActions'; 5 | import CardContent from '@mui/material/CardContent'; 6 | import Button from '@mui/material/Button'; 7 | import Typography from '@mui/material/Typography'; 8 | 9 | export default function BasicCard({name, ip, os, color}) { 10 | return ( 11 | 12 | 13 | 14 | Name: {name} 15 | 16 | 17 | IP: {ip} 18 | 19 | 20 | OS: {os} 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | } -------------------------------------------------------------------------------- /client/components/Depl.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Card from '@mui/material/Card'; 3 | import CardContent from '@mui/material/CardContent'; 4 | import Typography from '@mui/material/Typography'; 5 | 6 | export default function Depl(props) { 7 | return ( 8 | 9 | 10 | 11 | Deployments 12 | 13 | {props.deplNumber} 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /client/components/DoughnutChart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Doughnut } from 'react-chartjs-2'; 3 | import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; 4 | ChartJS.register(ArcElement, Tooltip, Legend); 5 | 6 | const data = { 7 | labels: ['Node 1', 'Node 2', 'Node 3', 'Node 4'], 8 | datasets: [{ 9 | data: [20, 30, 35, 15], 10 | backgroundColor: [ 11 | 'rgba(255, 99, 132, 0.2)', 12 | 'rgba(75, 192, 192, 0.2)', 13 | 'rgba(54, 162, 235, 0.2)', 14 | 'rgba(255, 206, 86, 0.2)', 15 | ], 16 | borderColor: [ 17 | 'rgba(255, 99, 132, 1)', 18 | 'rgba(75, 192, 192, 1)', 19 | 'rgba(54, 162, 235, 1)', 20 | 'rgba(255, 206, 86, 1)', 21 | 22 | ], 23 | borderWidth: 1, 24 | }] 25 | } 26 | 27 | export default function DoughnutChart() { 28 | return ( 29 |
30 |

CPU Usage

31 | 32 |
33 | ) 34 | } -------------------------------------------------------------------------------- /client/components/GaugeChartTemplate.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 | ArcElement, 11 | } from 'chart.js'; 12 | import { Doughnut } from 'react-chartjs-2'; 13 | import { Paper, Typography } from '@mui/material'; 14 | import ChartDataLabels from 'chartjs-plugin-datalabels'; 15 | 16 | 17 | function GaugeChartTemplate({ chartData, title, label }) { 18 | ChartJS.register( 19 | CategoryScale, 20 | LinearScale, 21 | BarElement, 22 | Title, 23 | Tooltip, 24 | Legend, 25 | ArcElement, 26 | ChartDataLabels 27 | ); 28 | 29 | const options = { 30 | plugins: { 31 | title: { 32 | display: false, 33 | responsive: true, 34 | animation: { 35 | animateScale: true, 36 | }, 37 | }, 38 | datalabels: { 39 | // This code is used to display data values 40 | anchor: 'center', 41 | align: 'center', 42 | font: { 43 | weight: 'bold', 44 | size: 20, 45 | }, 46 | formatter: (value) => value + '%', 47 | color: 'white', 48 | }, 49 | }, 50 | layout: { 51 | padding: { 52 | top: '-50', 53 | }, 54 | }, 55 | pointRadius: 0, 56 | circumference: '180', 57 | rotation: '-90', 58 | }; 59 | 60 | let utilColor; 61 | 62 | if (chartData) { 63 | if (100 - chartData <= 33.3) { 64 | utilColor = '#ea2315'; 65 | } else if (100 - chartData > 33.3 && 100 - chartData < 66.6) { 66 | utilColor = 'yellow'; 67 | } else { 68 | utilColor = 'rgba(39, 244, 142, 1)'; 69 | } 70 | } 71 | 72 | const data = { 73 | datasets: [ 74 | { 75 | data: [chartData, 100 - chartData], 76 | borderColor: ['transparent', 'transparent'], 77 | backgroundColor: [utilColor, '#808080'], 78 | }, 79 | ], 80 | }; 81 | 82 | const renderChartData = () => { 83 | if (chartData !== undefined) { 84 | return {chartData.toString() + '%'}; 85 | } 86 | }; 87 | 88 | return ( 89 | 90 | <> 91 | 92 | {title} 93 | 94 | 95 | 96 | ); 97 | } 98 | 99 | export default GaugeChartTemplate; -------------------------------------------------------------------------------- /client/components/LineChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | PointElement, 7 | LineElement, 8 | Title, 9 | Tooltip, 10 | Legend, 11 | } from 'chart.js'; 12 | import { Line } from 'react-chartjs-2'; 13 | // import our data from Prometheus eventually 14 | 15 | ChartJS.register( 16 | CategoryScale, 17 | LinearScale, 18 | PointElement, 19 | LineElement, 20 | Title, 21 | Tooltip, 22 | Legend 23 | ); 24 | 25 | export const options = { 26 | responsive: true, 27 | plugins: { 28 | legend: { 29 | position: 'top', // as const, 30 | }, 31 | title: { 32 | display: true, 33 | text: 'Memory Usage of Each Namespace in MB', 34 | }, 35 | }, 36 | }; 37 | 38 | // labels is x-values 39 | 40 | const labels = [ 41 | '0.00 ms', 42 | '500.00 ms', 43 | '1000.00 ms', 44 | '1500.00 ms', 45 | '2000.00 ms', 46 | '2500.00 ms', 47 | '3000.00 ms', 48 | '3500.00 ms', 49 | '4000.00 ms', 50 | '4500.00 ms', 51 | '5000.00 ms', 52 | '5500.00 ms', 53 | '6000.00 ms', 54 | '6500.00 ms', 55 | '7000.00 ms', 56 | '7500.00 ms, 8000.00 ms', 57 | '8500.00 ms', 58 | '9000.00 ms', 59 | ]; 60 | 61 | //dummyData are the y-values 62 | const dummyData = [ 63 | 14, 45, 32, 7, 2, 22, 5, 56, 6, 12, 23, 200, 60, 260, 70, 267, 220, 225, 64 | ]; 65 | const dummyData2 = [ 66 | 5, 6, 8, 1, 20, 110, 55, 6, 8, 60, 20, 18, 32, 23, 75, 47, 89, 200, 67 | ]; 68 | const dummyData3 = [ 69 | 5, 6, 8, 1, 20, 18, 5, 6, 8, 1, 20, 18, 32, 23, 75, 47, 89, 200, 70 | ]; 71 | const dummyData4 = [ 72 | 1, 2, 3, 2, 12, 18, 7, 6, 8, 11, 20, 18, 180, 230, 98, 10, 89, 20, 73 | ]; 74 | 75 | export const data = { 76 | //labels is x-values 77 | labels, 78 | // data: dummyData, are the y-values 79 | datasets: [ 80 | { 81 | label: 'Network Utilization', 82 | data: dummyData, 83 | borderColor: 'rgb(255, 99, 132)', 84 | backgroundColor: 'rgba(255, 99, 132, 0.5)', 85 | }, 86 | { 87 | label: 'Network Utilization 2', 88 | data: dummyData2, 89 | borderColor: 'rgb(53, 162, 235)', 90 | backgroundColor: 'rgba(53, 162, 235, 0.5)', 91 | }, 92 | { 93 | label: 'Network Utilization 3', 94 | data: dummyData3, 95 | borderColor: 'rgb(53, 216, 235)', 96 | backgroundColor: 'rgba(53, 216, 235, 0.5)', 97 | }, 98 | { 99 | label: 'Network Utilization 4', 100 | data: dummyData4, 101 | borderColor: 'rgb(153, 162, 235)', 102 | backgroundColor: 'rgba(153, 162, 235, 0.5)', 103 | }, 104 | ], 105 | }; 106 | 107 | export default function LineChart({lineData}) { 108 | console.log("lineData --> ", lineData); 109 | 110 | 111 | // iterate thru the object, skip the undefined (first element) 112 | 113 | //Access the array of each of the key 114 | //each array will contain an array of y values 115 | // x - time (30 min) -- labels / and y - values are data 116 | 117 | // the will be in byte, divide by a million to make in mega bytes 118 | 119 | 120 | const data2 = {}; 121 | data2.labels = []; 122 | let minCount = 0; 123 | console.log("this console" ,lineData['default']) 124 | for(let i = 0; i < lineData['default'].length; i++ ) //the count of x-values depend on the length of the array of each key 125 | { 126 | data2.labels.push(`${minCount = minCount + 30}min`); 127 | } 128 | // for each dataset we will have an obj 129 | data2.datasets = []; 130 | 131 | for(let namespace in lineData) 132 | { 133 | if(namespace !== 'undefined') 134 | { 135 | const namespaceMemory = {}; 136 | namespaceMemory.label = namespace; 137 | namespaceMemory.data = []; 138 | const outerArr = lineData[namespace] //outer array contains array of mem usage 139 | outerArr.forEach( innerArr => {namespaceMemory.data.push(innerArr[0] / 1000000)}); //take the first value of each inner array 140 | //random colors from Details Container 141 | const r = Math.floor(Math.random() * 255); 142 | const g = Math.floor(Math.random() * 255); 143 | const b = Math.floor(Math.random() * 255); 144 | namespaceMemory.borderColor = `rgba(${r},${g},${b})`; 145 | namespaceMemory.backgroundColor = `rgba(${r},${g},${b}, 0.5)`; 146 | 147 | data2.datasets.push(namespaceMemory); 148 | } 149 | } 150 | 151 | // console.log('lets get down', data2); 152 | //console.log('data 1', data); 153 | 154 | return ; 155 | } 156 | -------------------------------------------------------------------------------- /client/components/Namespace.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Card from '@mui/material/Card'; 3 | import CardContent from '@mui/material/CardContent'; 4 | import Typography from '@mui/material/Typography'; 5 | 6 | export default function Namespace(props) { 7 | return ( 8 | 9 | 10 | 11 | Namespace 12 | 13 | {props.namespaceNumber} 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /client/components/Node.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Card from '@mui/material/Card'; 3 | import CardContent from '@mui/material/CardContent'; 4 | import Typography from '@mui/material/Typography'; 5 | 6 | export default function Node(props) { 7 | return ( 8 | 9 | 10 | 11 | Nodes 12 | 13 | {props.nodeNumber} 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /client/components/NotificationCard.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Box from '@mui/material/Box'; 3 | import Card from '@mui/material/Card'; 4 | import CardActions from '@mui/material/CardActions'; 5 | import CardContent from '@mui/material/CardContent'; 6 | import Button from '@mui/material/Button'; 7 | import Typography from '@mui/material/Typography'; 8 | import { red } from '@mui/material/colors'; 9 | 10 | export default function CardForNoti({alertname, description, namespace, service, severity, state, activeAt, value, summary}) { 11 | 12 | const color = severity === 'critical' ? "red" : "orange"; 13 | 14 | return ( 15 | 16 | 17 | 18 | Alert Name: {alertname} 19 | 20 | 21 | Description: {description} 22 | 23 | 24 | Namespace: {namespace} 25 | 26 | 27 | Service: {service} 28 | 29 | 30 | Severity: {severity} 31 | 32 | 33 | State: {state} 34 | 35 | 36 | Active at: {activeAt} 37 | 38 | 39 | Value: {value} 40 | 41 | 42 | Summary: {summary} 43 | 44 | 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /client/components/PieChart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; 3 | import { Pie } from 'react-chartjs-2'; 4 | 5 | ChartJS.register(ArcElement, Tooltip, Legend); 6 | 7 | export const data = { 8 | labels: ['Node 1', 'Node 2', 'Node 3', 'Node 4'], 9 | datasets: [ 10 | { 11 | label: '# of Votes', 12 | data: [25, 40, 20, 15], 13 | backgroundColor: [ 14 | 'rgba(255, 99, 132, 0.2)', 15 | 'rgba(75, 192, 192, 0.2)', 16 | 'rgba(54, 162, 235, 0.2)', 17 | 'rgba(255, 206, 86, 0.2)', 18 | ], 19 | borderColor: [ 20 | 'rgba(255, 99, 132, 1)', 21 | 'rgba(75, 192, 192, 1)', 22 | 'rgba(54, 162, 235, 1)', 23 | 'rgba(255, 206, 86, 1)', 24 | ], 25 | borderWidth: 1, 26 | }, 27 | ], 28 | }; 29 | 30 | export default function PieChart() { 31 | return ( 32 |
33 |

Memory Usage

34 | 35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /client/components/Pods.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Card from '@mui/material/Card'; 3 | import CardContent from '@mui/material/CardContent'; 4 | import Typography from '@mui/material/Typography'; 5 | 6 | export default function Pods(props) { 7 | return ( 8 | 9 | 10 | 11 | Pods 12 | 13 | {props.podNumber} 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /client/components/PolarAreaChart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Chart as ChartJS, 4 | RadialLinearScale, 5 | ArcElement, 6 | Tooltip, 7 | Legend, 8 | } from 'chart.js'; 9 | import { PolarArea } from 'react-chartjs-2'; 10 | 11 | ChartJS.register(RadialLinearScale, ArcElement, Tooltip, Legend); 12 | 13 | export const data = { 14 | labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], 15 | datasets: [ 16 | { 17 | label: '# of Votes', 18 | data: [17, 23, 16, 8, 6, ], 19 | backgroundColor: [ 20 | 'rgba(255, 99, 132, 0.5)', 21 | 'rgba(54, 162, 235, 0.5)', 22 | 'rgba(255, 206, 86, 0.5)', 23 | 'rgba(75, 192, 192, 0.5)', 24 | 'rgba(153, 102, 255, 0.5)', 25 | 'rgba(255, 159, 64, 0.5)', 26 | ], 27 | borderWidth: 1, 28 | }, 29 | ], 30 | }; 31 | 32 | export default function PolarAreaChart() { 33 | return ; 34 | } 35 | -------------------------------------------------------------------------------- /client/components/SearchBar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { IconButton, Stack, TextField } from "@mui/material"; 3 | import SearchIcon from '@mui/icons-material/Search'; 4 | 5 | const Searcher = (props) => { 6 | 7 | const { setInputUser } = props; 8 | 9 | const [valueInput, setValueInput] = useState(''); 10 | 11 | const onSearchValueChange = (event) =>{ 12 | const inputValue = event.target.value; 13 | setValueInput(inputValue); 14 | } 15 | 16 | const handleSubmit = () => { 17 | setInputUser(valueInput); 18 | } 19 | 20 | return( 21 | 28 | 40 | 46 | 47 | 48 | 49 | ) 50 | } 51 | 52 | export default Searcher; 53 | -------------------------------------------------------------------------------- /client/components/Service.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Card from '@mui/material/Card'; 3 | import CardContent from '@mui/material/CardContent'; 4 | import Typography from '@mui/material/Typography'; 5 | 6 | export default function Service(props) { 7 | return ( 8 | 9 | 10 | 11 | Services 12 | 13 | {props.serviceNumber} 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /client/components/VerticalLineChart.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 | 13 | ChartJS.register( 14 | CategoryScale, 15 | LinearScale, 16 | BarElement, 17 | Title, 18 | Tooltip, 19 | Legend 20 | ); 21 | 22 | export const options = { 23 | responsive: true, 24 | plugins: { 25 | legend: { 26 | position: 'top', 27 | }, 28 | title: { 29 | display: true, 30 | text: 'Pod Memory and Performance', 31 | }, 32 | }, 33 | }; 34 | 35 | const labels = ['0.00 ms', '500.00 ms', '1000.00 ms', '1500.00 ms', '2000.00 ms', '2500.00 ms', '3000.00 ms']; 36 | const dummyData = [0, 300, 40, 500, 624, 982]; 37 | const dummyData2 = [0, 10, 100, 120,500, 999]; 38 | 39 | export const data = { 40 | labels, 41 | datasets: [ 42 | { 43 | label: 'Pod Capacity 1', 44 | data: dummyData, 45 | backgroundColor: 'rgba(255, 99, 132, 0.5)', 46 | }, 47 | 48 | { 49 | label: 'Pod Capacity 2', 50 | data: dummyData2, 51 | backgroundColor: 'rgba(53, 162, 235, 0.5)', 52 | }, 53 | ], 54 | }; 55 | 56 | export default function VerticalLineChart({allPodsData}) { 57 | 58 | const data2 = {}; 59 | data2.labels = []; 60 | let minCount = 0; 61 | for(let i = 0; i < allPodsData['undefined'].length; i++ ) //the count of x-values depend on the length of the array of each key 62 | { 63 | data2.labels.push(`${minCount = minCount + 30}min`); 64 | } 65 | // for each dataset we will have an obj 66 | data2.datasets = []; 67 | 68 | for(let podName in allPodsData) 69 | { 70 | if(podName !== 'undefined') 71 | { 72 | const podNameMemory = {}; 73 | podNameMemory.label = podName; 74 | podNameMemory.data = []; 75 | const outerArr = allPodsData[podName] //outer array contains array of mem usage 76 | outerArr.forEach( innerArr => {podNameMemory.data.push(innerArr[0] % 10000)}); //take the first value of each inner array 77 | //random colors from Details Container 78 | const r = Math.floor(Math.random() * 255); 79 | const g = Math.floor(Math.random() * 255); 80 | const b = Math.floor(Math.random() * 255); 81 | podNameMemory.backgroundColor = `rgba(${r},${g},${b}, 0.5)`; 82 | 83 | data2.datasets.push(podNameMemory); 84 | } 85 | } 86 | 87 | console.log('lets get down', data2); 88 | console.log('data 1', data); 89 | 90 | 91 | return ; 92 | } 93 | -------------------------------------------------------------------------------- /client/containers/DetailsContainer.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Box from '@mui/material/Box'; 3 | import InputLabel from '@mui/material/InputLabel'; 4 | import MenuItem from '@mui/material/MenuItem'; 5 | import FormControl from '@mui/material/FormControl'; 6 | import Select from '@mui/material/Select'; 7 | import BasicCard from '../components/BasicCard'; 8 | import List from '@mui/material/List'; 9 | import ListItem from '@mui/material/ListItem'; 10 | import DoughnutChart from '../components/DoughnutChart'; 11 | import PieChart from '../components/PieChart'; 12 | import Grid from '@mui/material/Grid'; 13 | import { useEffect, useState } from 'react'; 14 | import GaugeChartTemplate from '../components/GaugeChartTemplate' 15 | 16 | export default function DetailsContainer() { 17 | // For our drop down 18 | const [cpu, setCpu] = useState(0); 19 | const [mem, setMem] = useState(0); 20 | const [podData, setPodData] = useState({}); 21 | const [podsInfo, setPodsInfo] = useState([]); 22 | 23 | const handleChange = (event) => { 24 | console.log(event.target.value); 25 | const data = event.target.value; 26 | 27 | const pods = []; 28 | data.forEach( (element, i) => { 29 | 30 | //for each pod of podsInfo 31 | const pod = {}; 32 | 33 | pod.name = element['Pod Name']; 34 | pod.ip = element['Pod IP']; 35 | pod.os = 'Linux'; 36 | 37 | //assign random color 38 | const r = Math.floor(Math.random() * 255); 39 | const g = Math.floor(Math.random() * 255); 40 | const b = Math.floor(Math.random() * 255); 41 | pod.color = `rgba(${r},${g},${b},1)`; 42 | pod.key = {i}; 43 | 44 | pods.push(pod); 45 | }) 46 | 47 | 48 | setPodsInfo(pods); 49 | }; 50 | 51 | 52 | useEffect(() => { 53 | 54 | fetch('/api/k8s/podInfo') 55 | .then((response) => response.json()) 56 | .then((data) => { 57 | 58 | data.forEach( (element, i) => { 59 | 60 | //for each pod of podsInfo 61 | const pod = {}; 62 | 63 | pod.name = element.metadata.generateName; 64 | pod.ip = element.status.podIP; 65 | pod.os = 'Linux'; 66 | //assign random color 67 | const r = 0; 68 | const g = 138; 69 | const b = 216; 70 | pod.color = `rgba(${r},${g},${b},1)`; 71 | pod.key = {i}; 72 | 73 | setPodsInfo((prev) => ([...prev,pod])); 74 | }) 75 | }); 76 | 77 | //console.log(podsInfo); 78 | //---- FETCH REQ FOR THE 1ST SPEEDOMETER ---- CPU USAGE OF THE CLUSTER 79 | fetch('/api/k8s/promClusterCpuPct') 80 | .then((response) => response.json()) 81 | .then((data) => { 82 | setCpu(data); 83 | }); 84 | 85 | //---- FETCH REQ FOR THE 2ND SPEEDOMETER ---- CPU MEMORY OF THE CLUSTER 86 | fetch('/api/k8s/promClusterMemoryUtil') 87 | .then((response) => response.json()) 88 | .then((data) => { 89 | setMem(data); 90 | }); 91 | 92 | // get pod info based for each namespace 93 | fetch('/api/k8s/podData') 94 | .then((response) => response.json()) 95 | .then((data) => { 96 | setPodData(data); 97 | }); 98 | 99 | },[]) 100 | 101 | 102 | return ( 103 | <> 104 | 105 | 106 | Namespace 107 | 124 | 125 |
126 |
127 |
128 | 129 | 130 | {podsInfo.map((pod) => ( 131 | 132 | 138 | 139 | 140 | ))} 141 | 142 |
143 |
144 |
145 | 146 | 147 | 148 | 153 | 154 | 155 | 161 | 162 | 163 | 164 | 165 | {/* */} 166 | 167 | ); 168 | } 169 | -------------------------------------------------------------------------------- /client/containers/HomeContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import List from '@mui/material/List'; 3 | import ListItem from '@mui/material/ListItem'; 4 | import Node from '../components/Node'; 5 | import Pods from '../components/Pods'; 6 | import Depl from '../components/Depl'; 7 | import Service from '../components/Service'; 8 | import Namespace from '../components/Namespace'; 9 | import LineChart from '../components/LineChart'; 10 | import Grid from '@mui/material/Grid'; 11 | import VerticalLineChart from '../components/VerticalLineChart'; 12 | import PolarAreaChart from '../components/PolarAreaChart'; 13 | import AreaChart from '../components/AreaChart'; 14 | 15 | export default function HomeContainer() { 16 | //fetch request originally in Pods.jsx 17 | const [podNumber, setPodNumber] = useState([]); 18 | const [nodeNumber, setNodeNumber] = useState([]); 19 | const [deplNumber, setDeplNumber] = useState([]); 20 | const [serviceNumber, setServiceNumber] = useState([]); 21 | const [namespaceNumber, setNamespaceNumber] = useState([]); 22 | const [lineData, setLineData] = useState({}); 23 | const [allPodsData, setAllPodsData] = useState({}); 24 | const [networkReceived, setNetworkReceived] = useState({}); 25 | const [networkTransmitted, setNetworkTransmitted] = useState({}); 26 | 27 | 28 | 29 | 30 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 31 | /////////////////////////////////////// --- FETCH REQ FROM KUBERNETES DASHBOARD ------////////////////////////////////////////////// 32 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 33 | 34 | // useEffect counts the state 35 | useEffect(() => { 36 | //CHANGED THE ALLNAMESPACES TO THE BEGGINING 37 | //return pod count 38 | 39 | fetch('/api/k8s/podCount') 40 | .then((response) => response.json()) 41 | .then((data) => { 42 | setPodNumber(data.length); 43 | console.log('POD COUNT: ', data.length); 44 | }); 45 | 46 | // return node count 47 | fetch('/api/k8s/node') 48 | .then((response) => response.json()) 49 | .then((data) => { 50 | setNodeNumber(data.length); 51 | console.log('NODE COUNT: ', data.length); 52 | }); 53 | 54 | // return deployment count 55 | fetch('/api/k8s/deployment') 56 | .then((response) => response.json()) 57 | .then((data) => { 58 | setDeplNumber(data.length); 59 | console.log('DEPL COUNT: ', data.length); 60 | }); 61 | 62 | // return services count 63 | fetch('/api/k8s/services') 64 | .then((response) => response.json()) 65 | .then((data) => { 66 | setServiceNumber(data.length); 67 | console.log('SERVICES COUNT: ', data.length); 68 | }); 69 | 70 | // fetch req for namespaces 71 | fetch('/api/k8s/namespace') 72 | .then((response) => response.json()) 73 | .then((data) => { 74 | setNamespaceNumber(data.length); 75 | console.log('NAMESPACEX COUNT', data.length); 76 | }); 77 | 78 | //fetch req for line graph data (memory used for all namespaces) 79 | fetch('/api/k8s/memoryAllNamespaces') 80 | .then((response) => response.json()) 81 | .then((data) => { 82 | setLineData(data); 83 | }); 84 | 85 | 86 | 87 | //fetch req for vertical line graph data (memory used for all pods) 88 | fetch('/api/k8s/memoryAllPods') 89 | .then((response) => response.json()) 90 | .then((data) => { 91 | setAllPodsData(data); 92 | }); 93 | 94 | ///api/k8s/clusterNetRec 95 | //fetch req for area chart (Network Received) 96 | fetch('/api/k8s/clusterNetRec') 97 | .then((response) => response.json()) 98 | .then((data) => { 99 | setNetworkReceived(data); 100 | console.log('network received inside homecontainer', networkReceived); 101 | }); 102 | 103 | 104 | ///api/k8s/clusterNetTrans 105 | //fetch req for area chart (Network Transmitted) 106 | fetch('/api/k8s/clusterNetTrans') 107 | .then((response) => response.json()) 108 | .then((data) => { 109 | setNetworkTransmitted(data); 110 | }); 111 | },[]); 112 | 113 | 114 | 115 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 116 | /////////////////////////////////////// --- FETCH REQ FROM PROMETHEUS ------////////////////////////////////////////////// 117 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 118 | 119 | return ( 120 | <> 121 | 122 | 125 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | {' '} 141 | 142 | { lineData.default && } 143 | 144 | 145 | {' '} 146 | { allPodsData.undefined && } 147 | 148 | 149 |
150 |
151 |
152 |
153 | 154 | 155 | {' '} 156 | { networkTransmitted.undefined && } 157 | 158 | 159 | {' '} 160 | { networkReceived.undefined && } 161 | 162 | 163 | 164 | 165 | 166 | 167 | ); 168 | } 169 | -------------------------------------------------------------------------------- /client/containers/MiniDrawer.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { styled, useTheme } from '@mui/material/styles'; 3 | import Box from '@mui/material/Box'; 4 | import MuiDrawer from '@mui/material/Drawer'; 5 | import MuiAppBar from '@mui/material/AppBar'; 6 | import Toolbar from '@mui/material/Toolbar'; 7 | import List from '@mui/material/List'; 8 | import CssBaseline from '@mui/material/CssBaseline'; 9 | import Typography from '@mui/material/Typography'; 10 | import Divider from '@mui/material/Divider'; 11 | import IconButton from '@mui/material/IconButton'; 12 | import MenuIcon from '@mui/icons-material/Menu'; 13 | import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; 14 | import ChevronRightIcon from '@mui/icons-material/ChevronRight'; 15 | import ListItem from '@mui/material/ListItem'; 16 | import ListItemButton from '@mui/material/ListItemButton'; 17 | import ListItemIcon from '@mui/material/ListItemIcon'; 18 | import ListItemText from '@mui/material/ListItemText'; 19 | import InboxIcon from '@mui/icons-material/MoveToInbox'; 20 | import MailIcon from '@mui/icons-material/Mail'; 21 | import { Link } from 'react-router-dom'; 22 | import neptune2 from "../Launch_Material/logo/neptune2.png" 23 | import { Container } from "@mui/material"; 24 | 25 | const drawerWidth = 240; 26 | 27 | const openedMixin = (theme) => ({ 28 | width: drawerWidth, 29 | transition: theme.transitions.create('width', { 30 | easing: theme.transitions.easing.sharp, 31 | duration: theme.transitions.duration.enteringScreen, 32 | }), 33 | overflowX: 'hidden', 34 | }); 35 | 36 | const closedMixin = (theme) => ({ 37 | transition: theme.transitions.create('width', { 38 | easing: theme.transitions.easing.sharp, 39 | duration: theme.transitions.duration.leavingScreen, 40 | }), 41 | overflowX: 'hidden', 42 | width: `calc(${theme.spacing(7)} + 1px)`, 43 | [theme.breakpoints.up('sm')]: { 44 | width: `calc(${theme.spacing(8)} + 1px)`, 45 | }, 46 | }); 47 | 48 | const DrawerHeader = styled('div')(({ theme }) => ({ 49 | display: 'flex', 50 | alignItems: 'center', 51 | justifyContent: 'flex-end', 52 | padding: theme.spacing(0, 1), 53 | // necessary for content to be below app bar 54 | ...theme.mixins.toolbar, 55 | })); 56 | 57 | const AppBar = styled(MuiAppBar, { 58 | shouldForwardProp: (prop) => prop !== 'open', 59 | })(({ theme, open }) => ({ 60 | zIndex: theme.zIndex.drawer + 1, 61 | transition: theme.transitions.create(['width', 'margin'], { 62 | easing: theme.transitions.easing.sharp, 63 | duration: theme.transitions.duration.leavingScreen, 64 | }), 65 | ...(open && { 66 | marginLeft: drawerWidth, 67 | width: `calc(100% - ${drawerWidth}px)`, 68 | transition: theme.transitions.create(['width', 'margin'], { 69 | easing: theme.transitions.easing.sharp, 70 | duration: theme.transitions.duration.enteringScreen, 71 | }), 72 | }), 73 | })); 74 | 75 | const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })( 76 | ({ theme, open }) => ({ 77 | width: drawerWidth, 78 | flexShrink: 0, 79 | whiteSpace: 'nowrap', 80 | boxSizing: 'border-box', 81 | ...(open && { 82 | ...openedMixin(theme), 83 | '& .MuiDrawer-paper': openedMixin(theme), 84 | }), 85 | ...(!open && { 86 | ...closedMixin(theme), 87 | '& .MuiDrawer-paper': closedMixin(theme), 88 | }), 89 | }), 90 | ); 91 | 92 | export default function MiniDrawer({ children }) { 93 | const theme = useTheme(); 94 | const [open, setOpen] = React.useState(false); 95 | 96 | const handleDrawerOpen = () => { 97 | setOpen(true); 98 | }; 99 | 100 | const handleDrawerClose = () => { 101 | setOpen(false); 102 | }; 103 | 104 | return ( 105 | 106 | 107 | 108 | 109 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | {/* */} 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | {theme.direction === 'rtl' ? : } 137 | 138 | 139 | 140 |
141 | 142 | {[ 143 | { text: 'Home', path: '/', icon: 'house' }, 144 | { text: 'Details', path: '/details', icon: 'speed' }, 145 | { text: 'Notifications', path: '/alerts', icon: 'notifications' }, 146 | ].map((menu, index) => ( 147 | 148 | 149 | 156 | 163 | 164 | {menu.icon} 165 | 166 | 167 | 168 | 169 | 170 | 171 | ))} 172 | 173 |
174 |
175 | 176 | 177 | { children } 178 | 179 |
180 | ); 181 | } 182 | -------------------------------------------------------------------------------- /client/containers/NotificationContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SearchBar from '../components/SearchBar'; 3 | import Box from '@mui/material/Box'; 4 | import InputLabel from '@mui/material/InputLabel'; 5 | import MenuItem from '@mui/material/MenuItem'; 6 | import FormControl from '@mui/material/FormControl'; 7 | import Select from '@mui/material/Select'; 8 | import BasicCard from '../components/BasicCard'; 9 | import List from '@mui/material/List'; 10 | import ListItem from '@mui/material/ListItem'; 11 | import Grid from '@mui/material/Grid'; 12 | import { useEffect, useState } from 'react'; 13 | import NotificationCard from '../components/NotificationCard'; 14 | 15 | 16 | 17 | 18 | 19 | export default function NotisContainer (){ 20 | const [namespace, setNamespace] = React.useState(''); 21 | 22 | const handleChange = (event) => { 23 | setNamespace(event.target.value); 24 | }; 25 | 26 | const [notis, setNotis] = useState([]); 27 | useEffect(() => { 28 | 29 | fetch('api/k8s/promAlerts') 30 | .then((response) => response.json()) 31 | .then((data) => { 32 | 33 | // nodesInfo.push(node); 34 | setNotis((prev) => ([...data])); 35 | }) 36 | 37 | },[]) 38 | 39 | return ( 40 | <> 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Sort By 49 | 60 | 61 |
62 |
63 |
64 |
65 |
66 | 67 | 68 | {notis.map((noti) => ( 69 | 70 | 71 | 82 | 83 | 84 | ))} 85 | 86 | 87 | 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /client/containers/QueryContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function QueryContainer (){ 4 | return ( 5 | <> 6 |

This is Query container

7 | 8 | ); 9 | } -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from './App.jsx' 4 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 5 | import { Container } from '@mui/material'; 6 | 7 | // instead of using regular rendering, we will be using the root method for react v 18.0 8 | const root = ReactDOM.createRoot(document.getElementById('root')); 9 | 10 | root.render( 11 | <> 12 | 13 | 14 | ) -------------------------------------------------------------------------------- /client/styles/style.css: -------------------------------------------------------------------------------- 1 | .gaugeChartClass { 2 | margin-top: -150px; 3 | } 4 | 5 | /* 6 | .App { 7 | background-color: black; 8 | } 9 | */ 10 | 11 | .css-1pnmrwp-MuiTypography-root{ 12 | font-weight:700px; 13 | font-size:2rem; 14 | text-align: center; 15 | } 16 | 17 | 18 | .css-pwngrv-MuiPaper-root-MuiCard-root{ 19 | background-color:#ffffff; 20 | } 21 | 22 | 23 | /* 24 | 25 | .AppGlass { 26 | display: grid; 27 | height: 97%; 28 | width: 97%; 29 | border-radius: 2rem; 30 | background-color: blue; 31 | } */ 32 | 33 | 34 | /* This is the header with the logo */ 35 | /* top nav bar */ 36 | .css-1nt9mjz-MuiToolbar-root{ 37 | background-color:#073072; 38 | min-height: 80px; 39 | 40 | } 41 | 42 | 43 | .css-12i7wg6-MuiPaper-root-MuiDrawer-paper{ 44 | background-color: #f2f7ff; 45 | width: 5%; 46 | border-radius: 16px; 47 | } 48 | 49 | /* Icon box */ 50 | .css-h4y409-MuiList-root{ 51 | /* padding-top: 700%; */ 52 | /* vertical-align: middle; */ 53 | } 54 | 55 | 56 | .css-p55zm9-MuiButtonBase-root-MuiIconButton-root { 57 | margin-left:-18px 58 | } 59 | 60 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Neptune 8 | 9 | 10 | 11 | /> 12 | 13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "neptune", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=production nodemon server/server.js", 8 | "build": "NODE_ENV=production webpack", 9 | "dev": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --open --hot --progress --color \" \"cross-env NODE_ENV=test nodemon ./server/server.js\"", 10 | "test": "jest" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/lawyeh/Neptune.git" 15 | }, 16 | "keywords": [], 17 | "author": "Jin Yoo, Lawrence Yeh, Miranda Jaramillo, Swan Htet", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/lawyeh/Neptune/issues" 21 | }, 22 | "homepage": "https://github.com/oslabs-beta/Neptune", 23 | "dependencies": { 24 | "@emotion/react": "^11.9.3", 25 | "@emotion/styled": "^11.9.3", 26 | "@kubernetes/client-node": "^0.16.3", 27 | "@mui/icons-material": "^5.8.4", 28 | "@mui/material": "^5.9.0", 29 | "@mui/x-data-grid": "^5.11.0", 30 | "@visx/group": "^2.10.0", 31 | "@visx/heatmap": "^2.10.0", 32 | "@visx/mock-data": "^2.1.2", 33 | "@visx/responsive": "^2.10.0", 34 | "@visx/scale": "^2.2.2", 35 | "chart.js": "^3.8.0", 36 | "chartjs-plugin-datalabels": "^2.0.0", 37 | "concurrently": "^7.2.2", 38 | "cors": "^2.8.5", 39 | "express": "^4.18.1", 40 | "fs": "0.0.1-security", 41 | "match-sorter": "^6.3.1", 42 | "node-cmd": "^5.0.0", 43 | "node-fetch": "^3.2.4", 44 | "prom-client": "^14.0.1", 45 | "react": "^18.1.0", 46 | "react-chartjs-2": "^4.3.1", 47 | "react-copy-to-clipboard": "^5.1.0", 48 | "react-dom": "^18.1.0", 49 | "react-router-dom": "^6.3.0", 50 | "react-scroll": "^1.8.7", 51 | "react-table": "^7.8.0", 52 | "ts-node": "^10.8.2", 53 | "webpack": "^5.72.1" 54 | }, 55 | "devDependencies": { 56 | "@babel/core": "^7.17.10", 57 | "@babel/preset-env": "^7.17.10", 58 | "@babel/preset-react": "^7.16.7", 59 | "babel-loader": "^8.2.5", 60 | "cross-env": "^7.0.3", 61 | "css-loader": "^6.7.1", 62 | "html-webpack-plugin": "^5.5.0", 63 | "jest": "^28.1.2", 64 | "jest-puppeteer": "^6.1.1", 65 | "mini-css-extract-plugin": "^2.6.0", 66 | "nodemon": "^2.0.19", 67 | "puppeteer": "^15.3.2", 68 | "react-select": "^5.3.2", 69 | "sass-loader": "^12.6.0", 70 | "style-loader": "^3.3.1", 71 | "supertest": "^6.2.3", 72 | "ts-loader": "^9.3.1", 73 | "webpack-cli": "^4.9.2", 74 | "webpack-dev-server": "^4.9.3" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /server/controllers/k8sClientController.js: -------------------------------------------------------------------------------- 1 | const k8s = require('@kubernetes/client-node'); 2 | 3 | //New instance of k8s, (Kube config are the files that connect ) 4 | const kc = new k8s.KubeConfig(); 5 | kc.loadFromDefault(); 6 | 7 | const k8sApiSvc = kc.makeApiClient(k8s.CoreV1Api); // Queries services 8 | const k8sApiDepl = kc.makeApiClient(k8s.AppsV1Api); // Queries deployments 9 | //const metricsClient = new k8s.Metrics(kc); 10 | 11 | k8sController = {}; 12 | 13 | // Getting pod count and pod names 14 | k8sController.getAllPods = async (req, res, next) => { 15 | try { 16 | const podsResult = await k8sApiSvc.listPodForAllNamespaces(); 17 | res.locals.podData = podsResult.response.body.items; 18 | res.locals.podCount = podsResult.body.items; 19 | const podNames = []; 20 | const podIps = []; 21 | const podData = {}; 22 | res.locals.podData.forEach((element) => { 23 | // if namespace does not exist in podData object 24 | if (!podData[element.metadata.namespace]) { 25 | // then create namespace as a property and assign podname, podip as values in an object 26 | podData[element.metadata.namespace] = [ 27 | { 28 | 'Pod Name': element.metadata.name, 29 | 'Pod IP': element.status.podIP, 30 | }, 31 | ]; 32 | // else if namespac exists AND 33 | } else if (podData[element.metadata.namespace]) { 34 | podData[element.metadata.namespace].push({ 35 | 'Pod Name': element.metadata.name, 36 | 'Pod IP': element.status.podIP, 37 | }); 38 | } 39 | // console.log('YODA', podData); 40 | res.locals.podData = podData; 41 | }); 42 | 43 | podsResult.body.items.forEach((element) => { 44 | podNames.push(element.metadata.generateName); 45 | podIps.push(element.status.podIP); 46 | }); 47 | res.locals.podNames = podNames; 48 | res.locals.podIps = podIps; 49 | res.locals.info = podsResult.body.items; 50 | return next(); 51 | } catch (err) { 52 | return next({ 53 | log: 'Error getting data from getAllPods', 54 | status: 404, 55 | message: { 56 | err: 'An error happened trying to get the data from getAllPods', 57 | }, 58 | }); 59 | } 60 | }; 61 | 62 | // Getting list of nodes and list of component statuses 63 | k8sController.getAllNodes = async (req, res, next) => { 64 | const { name } = req.params; 65 | const nodeNames = []; 66 | try { 67 | const nodeResult = await k8sApiSvc.listNode(name); 68 | nodeResult.response.body.items.forEach((element) => { 69 | nodeNames.push(element.metadata.name); 70 | }); 71 | 72 | res.locals.nodeList = nodeNames; 73 | // const nodeStatus = await k8sApiSvc.listComponentStatus(); 74 | // res.locals.nodeList.nodeStatus = nodeStatus.body; 75 | return next(); 76 | } catch (err) { 77 | return next({ 78 | log: 'Error getting data from getAllNodes', 79 | status: 500, 80 | message: { 81 | err: 'An error happened trying to get All Nodes', 82 | }, 83 | }); 84 | } 85 | }; 86 | 87 | // Getting number of namespaces and their names 88 | k8sController.getAllNamespaces = async (req, res, next) => { 89 | try { 90 | const namespaceResult = await k8sApiSvc.listNamespace(); 91 | res.locals.namespace = namespaceResult.response.body.items; 92 | const namespaceNames = []; 93 | namespaceResult.response.body.items.forEach((element) => { 94 | namespaceNames.push(element.metadata.name); 95 | }); 96 | res.locals.namespaceNames = namespaceNames; 97 | return next(); 98 | } catch (err) { 99 | return next({ 100 | log: 'Error getting data from getAllNamespaces', 101 | status: 500, 102 | message: { 103 | err: 'An error happened trying to get the data from getAllNamespaces', 104 | }, 105 | }); 106 | } 107 | }; 108 | 109 | // Getting all deployments in cluster 110 | k8sController.getDeployment = async (req, res, next) => { 111 | try { 112 | const getDeployment = await k8sApiDepl.listDeploymentForAllNamespaces(); 113 | res.locals.deployment = getDeployment.body.items; 114 | return next(); 115 | } catch (err) { 116 | return next({ 117 | log: 'Error getting data from getDeploymentList', 118 | status: 500, 119 | message: { 120 | err: 'An error happened trying to get the data from getDeploymentList', 121 | }, 122 | }); 123 | } 124 | }; 125 | 126 | // Getting all services in cluster 127 | k8sController.getService = async (req, res, next) => { 128 | try { 129 | const getService = await k8sApiSvc.listServiceForAllNamespaces(); 130 | res.locals.service = getService.body.items; 131 | return next(); 132 | } catch (err) { 133 | return next({ 134 | log: 'Error getting data from getServiceList', 135 | status: 500, 136 | message: { 137 | err: 'An error happened trying to get the data from getServiceList', 138 | }, 139 | }); 140 | } 141 | }; 142 | 143 | module.exports = k8sController; 144 | -------------------------------------------------------------------------------- /server/controllers/promController.js: -------------------------------------------------------------------------------- 1 | // kubectl port-forward prometheus-prometheus-kube-prometheus-prometheus-0 --namespace=default 9090:9090 2 | const { response } = require('express'); 3 | const fetch = (...args) => 4 | import('node-fetch').then(({ default: fetch }) => fetch(...args)); 5 | const queryURL = 'http://127.0.0.1:9090/api/v1/'; 6 | const promController = {}; 7 | 8 | //----------CLUSTER----------// 9 | 10 | //CLUSTER - Total CPU Cores 11 | promController.promClusterCpuCore = async (req, res, next) => { 12 | try { 13 | const response = await fetch( 14 | `${queryURL}query?query=count without(cpu, mode) (node_cpu_seconds_total{mode="idle"})` 15 | ); 16 | res.locals.promClusterCpuCore = await response.json(); 17 | res.locals.promClusterCpuCore = parseInt( 18 | res.locals.promClusterCpuCore.data.result[0].value[1] 19 | ); 20 | return next(); 21 | } catch (err) { 22 | return next({ 23 | log: 'Error getting data from promClusterCpuCore', 24 | status: 500, 25 | message: { 26 | err: 'An error happened trying to get the data from promClusterCpuCore', 27 | }, 28 | }); 29 | } 30 | }; 31 | 32 | // CLUSTER - CPU USAGE 33 | promController.promClusterCpuPct = async (req, res, next) => { 34 | try { 35 | const response = await fetch( 36 | `${queryURL}query?query=1 - sum(avg by (mode) (rate(node_cpu_seconds_total{job="node-exporter", mode=~"idle|iowait|steal"}[10m])))` 37 | ); 38 | data = await response.json(); 39 | data = Number(data.data.result[0].value[1]) * 100; 40 | res.locals.promClusterCpuPct = data; 41 | return next(); 42 | } catch (err) { 43 | return next({ 44 | log: 'Error getting data from promClusterCpuPct', 45 | status: 500, 46 | message: { 47 | err: 'An error happened trying to get the data from promClusterCpuPct', 48 | }, 49 | }); 50 | } 51 | }; 52 | 53 | // CLUSTER - TOTAL MEMORY 54 | promController.promClusterMemoryTotal = async (req, res, next) => { 55 | try { 56 | const response = await fetch( 57 | `${queryURL}query?query=sum(container_memory_working_set_bytes)` 58 | ); 59 | data = await response.json(); 60 | res.locals.promClusterMemoryTotal = data.data.result[0].value[1]; 61 | return next(); 62 | } catch (err) { 63 | return next({ 64 | log: 'Error getting data from promClusterMemoryTotal', 65 | status: 500, 66 | message: { 67 | err: 'An error happened trying to get the data from promClusterMemoryTotal', 68 | }, 69 | }); 70 | } 71 | }; 72 | 73 | // CLUSTER - MEMORY UTILIZATION 74 | promController.promClusterMemoryUtil = async (req, res, next) => { 75 | try { 76 | const response = await fetch( 77 | `${queryURL}query?query=node_memory_Active_bytes/node_memory_MemTotal_bytes` 78 | ); 79 | data = await response.json(); 80 | data = data.data.result[0].value[1] * 100; 81 | res.locals.promClusterMemoryUtil = data.toFixed(2); 82 | return next(); 83 | } catch (err) { 84 | return next({ 85 | log: 'Error getting data from promClusterMemoryUtil', 86 | status: 500, 87 | message: { 88 | err: 'An error happened trying to get the data from promClusterMemoryUtil', 89 | }, 90 | }); 91 | } 92 | }; 93 | 94 | // CLUSTER - TOTAL MEMORY 95 | promController.promClusterMemoryTotal = async (req, res, next) => { 96 | try { 97 | const response = await fetch( 98 | `${queryURL}query?query=sum(container_memory_working_set_bytes)` 99 | ); 100 | data = await response.json(); 101 | res.locals.promClusterMemoryTotal = data.data.result[0].value[1]; 102 | return next(); 103 | } catch (err) { 104 | return next({ 105 | log: 'Error getting data from promClusterMemoryTotal', 106 | status: 500, 107 | message: { 108 | err: 'An error happened trying to get the data from promClusterMemoryTotal', 109 | }, 110 | }); 111 | } 112 | }; 113 | 114 | //----------NAMESPACES----------// 115 | 116 | // GETTING ALL THE NAMESPACES IN AN ARRAY 117 | promController.promNamespaces = async (req, res, next) => { 118 | try { 119 | const response = await fetch( 120 | `${queryURL}query?query=kube_namespace_created` 121 | ); 122 | data = await response.json(); 123 | const namespaces = []; 124 | data.data.result.forEach((element) => { 125 | namespaces.push(element.metric.namespace); 126 | }); 127 | res.locals.promNamespaces = namespaces; 128 | return next(); 129 | } catch (err) { 130 | return next({ 131 | log: 'Error getting data from promNamespaces', 132 | status: 500, 133 | message: { 134 | err: 'An error happened trying to get the data from promNamespaces', 135 | }, 136 | }); 137 | } 138 | }; 139 | 140 | //----------NODES----------// 141 | 142 | // NODE - CPU usage 143 | promController.promNodeCpu = async (req, res, next) => { 144 | try { 145 | const response = await fetch( 146 | `${queryURL}query?query=(1 - sum by (instance)(increase(node_cpu_seconds_total{mode="idle"}[5m])) / sum by (instance)(increase(node_cpu_seconds_total[5m])))*100)` 147 | ); 148 | res.locals.promNodeCpu = await response.json(); 149 | res.locals.promNodeCpu = res.locals.promNodeCpu.data; 150 | return next(); 151 | } catch (err) { 152 | return next({ 153 | log: 'Error getting data from promNodeCpu', 154 | status: 500, 155 | message: { 156 | err: 'An error happened trying to get the data from promNodeCpu', 157 | }, 158 | }); 159 | } 160 | }; 161 | 162 | // NODE - Return pod capacity of node as a number 163 | promController.promNodePodCap = async (req, res, next) => { 164 | try { 165 | const response = await fetch( 166 | `${queryURL}query?query=kube_node_status_capacity{resource="pods"}` 167 | ); 168 | res.locals.promNodePodCap = await response.json(); 169 | res.locals.promNodePodCap = parseInt( 170 | res.locals.promNodePodCap.data.result[0].value[1] 171 | ); 172 | return next(); 173 | } catch (err) { 174 | return next({ 175 | log: 'Error getting data from promNodePodCap', 176 | status: 500, 177 | message: { 178 | err: 'An error happened trying to get the data from promNodePodCap', 179 | }, 180 | }); 181 | } 182 | }; 183 | 184 | // NODE - Return network utilization 185 | promController.promNodeNetUtil = async (req, res, next) => { 186 | try { 187 | const response = await fetch( 188 | `${queryURL}query?query=sum(rate(container_network_receive_bytes_total[5m]))` 189 | ); 190 | data = await response.json(); 191 | res.locals.promNodeNetUtil = data; 192 | res.locals.promNodeNetUtil = Number(data.data.result[0].value[1]); 193 | return next(); 194 | } catch (err) { 195 | return next({ 196 | log: 'Error getting data from promNodeNetUtil', 197 | status: 500, 198 | message: { 199 | err: 'An error happened trying to get the data from promNodeNetUtil', 200 | }, 201 | }); 202 | } 203 | }; 204 | 205 | // NODE - Return network errors 206 | promController.promNodeNetErr = async (req, res, next) => { 207 | try { 208 | const response1 = await fetch( 209 | `${queryURL}query?query=sum(node_network_receive_errs_total)` 210 | ); 211 | const receiveErr = await response1.json(); 212 | 213 | const response2 = await fetch( 214 | `${queryURL}query?query=sum(node_network_transmit_errs_total)` 215 | ); 216 | const transmitErr = await response2.json(); 217 | 218 | const networkErrors = Math.floor( 219 | (parseInt(receiveErr.data.result[0].value[1]) + 220 | parseInt(transmitErr.data.result[0].value[1])) / 221 | 1024 222 | ); 223 | 224 | res.locals.promNodeNetErr = networkErrors; 225 | 226 | return next(); 227 | } catch (err) { 228 | return next({ 229 | log: 'Error getting data from promNodeNetErr', 230 | status: 500, 231 | message: { 232 | err: 'An error happened trying to get the data from promNodeNetErr', 233 | }, 234 | }); 235 | } 236 | }; 237 | 238 | //----------ALERTS----------// 239 | 240 | // ALERTS 241 | promController.promAlerts = async (req, res, next) => { 242 | try { 243 | const response = await fetch(`${queryURL}alerts`); 244 | data = await response.json(); 245 | res.locals.promAlerts = data.data.alerts; 246 | return next(); 247 | } catch (err) { 248 | return next({ 249 | log: 'Error getting data from promAlerts', 250 | status: 500, 251 | message: { 252 | err: 'An error happened trying to get the data from promAlerts', 253 | }, 254 | }); 255 | } 256 | }; 257 | 258 | module.exports = promController; 259 | -------------------------------------------------------------------------------- /server/controllers/queryController.js: -------------------------------------------------------------------------------- 1 | // const { data } = require('../../client/components/PieChart'); 2 | const fetch = (...args) => 3 | import('node-fetch').then(({ default: fetch }) => fetch(...args)); 4 | const queryURL = 'http://127.0.0.1:9090/api/v1/'; 5 | const queryController = {}; 6 | 7 | // Returning all queries for PROMQL 8 | queryController.allQueries = async (req, res, next) => { 9 | try { 10 | const response = await fetch(`${queryURL}label/__name__/values`); 11 | data = await response.json(); 12 | res.locals.allQueries = data.data; 13 | return next(); 14 | } catch (err) { 15 | return next({ 16 | log: 'Error getting data from allQueries in queryController', 17 | status: 500, 18 | message: { 19 | err: 'An error happened trying to get the data from allQueries', 20 | }, 21 | }); 22 | } 23 | }; 24 | 25 | // Memory in bytes for all namespaces, returned in object, key is name of namespace, value is an array with data 26 | queryController.memoryAllNamespaces = async (req, res, next) => { 27 | let start = new Date(); 28 | let end = new Date(start.getTime()); 29 | end.setHours(end.getHours() - 24); 30 | let endDateTime = start.toISOString(); 31 | let startDateTime = end.toISOString(); 32 | const memoryCache = {}; 33 | const query = `${queryURL}query_range?query=sum(container_memory_working_set_bytes) by (namespace)&start=${startDateTime}&end=${endDateTime}&step=30m`; 34 | try { 35 | const response = await fetch(query); 36 | data = await response.json(); 37 | data.data.result.forEach((element) => { 38 | if (!memoryCache[element.metric.namespace]) { 39 | memoryCache[element.metric.namespace] = element.values; 40 | } 41 | }); 42 | res.locals.memoryAllNamespaces = memoryCache; 43 | return next(); 44 | } catch (err) { 45 | return next({ 46 | log: 'Error getting data from memoryAllNamespaces in queryController', 47 | status: 500, 48 | message: { 49 | err: 'An error happened trying to get the data from memoryAllNamespaces', 50 | }, 51 | }); 52 | } 53 | }; 54 | 55 | // Memory in bytes for all pods, returned in object, key is name of namespace, value is an array with data 56 | queryController.memoryAllPods = async (req, res, next) => { 57 | let start = new Date(); 58 | let end = new Date(start.getTime()); 59 | end.setHours(end.getHours() - 24); 60 | let endDateTime = start.toISOString(); 61 | let startDateTime = end.toISOString(); 62 | const memoryCache = {}; 63 | const query = `${queryURL}query_range?query=sum(container_memory_working_set_bytes) by (pod)&start=${startDateTime}&end=${endDateTime}&step=30m`; 64 | try { 65 | const response = await fetch(query); 66 | data = await response.json(); 67 | data.data.result.forEach((element) => { 68 | if (!memoryCache[element.metric.pod]) { 69 | memoryCache[element.metric.pod] = element.values; 70 | } 71 | }); 72 | res.locals.memoryAllPods = memoryCache; 73 | return next(); 74 | } catch (err) { 75 | return next({ 76 | log: 'Error getting data from memoryAllPods in queryController', 77 | status: 500, 78 | message: { 79 | err: 'An error happened trying to get the data from memoryAllPods', 80 | }, 81 | }); 82 | } 83 | }; 84 | 85 | // Get cluster network utilization - Transmit 86 | queryController.clusterNetRec = async (req, res, next) => { 87 | let start = new Date(); 88 | let end = new Date(start.getTime()); 89 | end.setHours(end.getHours() - 24); 90 | let endDateTime = start.toISOString(); 91 | let startDateTime = end.toISOString(); 92 | const memoryCache = {}; 93 | const query = `${queryURL}query_range?query=sum(irate(container_network_receive_bytes_total[10m])) by (namespace)&start=${startDateTime}&end=${endDateTime}&step=30m`; 94 | try { 95 | const response = await fetch(query); 96 | data = await response.json(); 97 | data.data.result.forEach((element) => { 98 | if (!memoryCache[element.metric.pod]) { 99 | memoryCache[element.metric.pod] = element.values; 100 | } 101 | }); 102 | res.locals.clusterNetRec = memoryCache; 103 | return next(); 104 | } catch (err) { 105 | return next({ 106 | log: 'Error getting data from clusterNetRec in queryController', 107 | status: 500, 108 | message: { 109 | err: 'An error happened trying to get the data from clusterNetRec', 110 | }, 111 | }); 112 | } 113 | }; 114 | 115 | // Get cluster network utilization - Receive 116 | queryController.clusterNetTrans = async (req, res, next) => { 117 | let start = new Date(); 118 | let end = new Date(start.getTime()); 119 | end.setHours(end.getHours() - 24); 120 | let endDateTime = start.toISOString(); 121 | let startDateTime = end.toISOString(); 122 | const memoryCache = {}; 123 | const query = `${queryURL}query_range?query=sum(irate(container_network_transmit_bytes_total[10m])) by (namespace)&start=${startDateTime}&end=${endDateTime}&step=30m`; 124 | try { 125 | const response = await fetch(query); 126 | data = await response.json(); 127 | data.data.result.forEach((element) => { 128 | if (!memoryCache[element.metric.pod]) { 129 | memoryCache[element.metric.pod] = element.values; 130 | } 131 | }); 132 | res.locals.clusterNetTrans = memoryCache; 133 | return next(); 134 | } catch (err) { 135 | return next({ 136 | log: 'Error getting data from clusterNetTrans in queryController', 137 | status: 500, 138 | message: { 139 | err: 'An error happened trying to get the data from clusterNetTrans', 140 | }, 141 | }); 142 | } 143 | }; 144 | 145 | module.exports = queryController; 146 | -------------------------------------------------------------------------------- /server/routes/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const k8sRouter = express.Router(); 3 | const k8sController = require('../controllers/k8sClientController'); 4 | const promController = require('../controllers/promController'); 5 | const queryController = require('../controllers/queryController'); 6 | 7 | /** 8 | * ************************************ 9 | * 10 | * @module kubernetes client API -requests 11 | * @description contains our routes for our k8's API client fetch requests 12 | * 13 | * ************************************ 14 | */ 15 | 16 | //----------PODS---------// 17 | 18 | // ROUTE FOR PODS, PODNAMES, PODIPS 19 | k8sRouter.get('/podCount', k8sController.getAllPods, (req, res) => { 20 | return res.status(200).json(res.locals.podCount); 21 | }); 22 | k8sRouter.get('/podNames', k8sController.getAllPods, (req, res) => { 23 | return res.status(200).json(res.locals.podNames); 24 | }); 25 | k8sRouter.get('/podIps', k8sController.getAllPods, (req, res) => { 26 | return res.status(200).json(res.locals.podIps); 27 | }); 28 | k8sRouter.get('/podData', k8sController.getAllPods, (req, res) => { 29 | return res.status(200).json(res.locals.podData); 30 | }); 31 | 32 | k8sRouter.get('/podInfo', k8sController.getAllPods, (req, res) => { 33 | return res.status(200).json(res.locals.info); 34 | }); 35 | 36 | //----------NODES----------// 37 | 38 | // ROUTE FOR NODE LIST AND NODE STATUS 39 | // returns node count 40 | k8sRouter.get('/node', k8sController.getAllNodes, (req, res) => { 41 | return res.status(200).json(res.locals.nodeList); 42 | }); 43 | 44 | // returns node cpu usage 45 | k8sRouter.get('/promNodeCpu', promController.promNodeCpu, (req, res) => { 46 | return res.status(200).json(res.locals.promNodeCpu); 47 | }); 48 | 49 | // return pod capacity of node as a number 50 | k8sRouter.get('/promNodePodCap', promController.promNodePodCap, (req, res) => { 51 | return res.status(200).json(res.locals.promNodePodCap); 52 | }); 53 | 54 | // returns node network utilization 55 | k8sRouter.get( 56 | '/promNodeNetUtil', 57 | promController.promNodeNetUtil, 58 | (req, res) => { 59 | return res.status(200).json(res.locals.promNodeNetUtil); 60 | } 61 | ); 62 | 63 | // returns node network errors 64 | k8sRouter.get('/promNodeNetErr', promController.promNodeNetErr, (req, res) => { 65 | return res.status(200).json(res.locals.promNodeNetErr); 66 | }); 67 | 68 | //----------NAMESPACES----------// 69 | 70 | // ROUTE FOR NUMBER OF NAMESPACES 71 | k8sRouter.get('/namespace', k8sController.getAllNamespaces, (req, res) => { 72 | return res.status(200).json(res.locals.namespace); 73 | }); 74 | // ROUTE FOR NAMESPACES NAMES 75 | k8sRouter.get('/namespaceNames', k8sController.getAllNamespaces, (req, res) => { 76 | return res.status(200).json(res.locals.namespaceNames); 77 | }); 78 | 79 | //----------DEPLOYMENTS----------// 80 | 81 | // ROUTE FOR DEPLOYMENTS 82 | k8sRouter.get('/deployment', k8sController.getDeployment, (req, res) => { 83 | return res.status(200).json(res.locals.deployment); 84 | }); 85 | 86 | //----------SERVICES----------// 87 | 88 | // ROUTE FOR SERVICES 89 | k8sRouter.get('/services', k8sController.getService, (req, res) => { 90 | return res.status(200).json(res.locals.service); 91 | }); 92 | 93 | /** 94 | * ************************************ 95 | * 96 | * @module promql-requests 97 | * @description contains our routes for our promethus fetch requests 98 | * 99 | * ************************************ 100 | */ 101 | 102 | //----------PROMETHEUS ROUTE HANDLERS----------// 103 | 104 | // TESTING NAMESPACES ******************** 105 | k8sRouter.get('/promNamespaces', promController.promNamespaces, (req, res) => { 106 | return res.status(200).json(res.locals.promNamespaces); 107 | }); 108 | 109 | //----------PROMETHEUS CLUSTERS----------// 110 | 111 | // PROMETHEUS CPU CORES 112 | k8sRouter.get( 113 | '/promClusterCpuCore', 114 | promController.promClusterCpuCore, 115 | (req, res) => { 116 | return res.status(200).json(res.locals.promClusterCpuCore); 117 | } 118 | ); 119 | 120 | // PROMETHEUS CPU UTILIZATION 121 | k8sRouter.get( 122 | '/promClusterCpuPct', 123 | promController.promClusterCpuPct, 124 | (req, res) => { 125 | return res.status(200).json(res.locals.promClusterCpuPct); 126 | } 127 | ); 128 | 129 | // // PROMETHEUS CLUSTER MEMORY UTILIZATION 130 | k8sRouter.get( 131 | '/promClusterMemoryUtil', 132 | promController.promClusterMemoryUtil, 133 | (req, res) => { 134 | return res.status(200).json(res.locals.promClusterMemoryUtil); 135 | } 136 | ); 137 | 138 | // // PROMETHEUS CLUSTER MEMORY TOTAL 139 | k8sRouter.get( 140 | '/promClusterMemoryTotal', 141 | promController.promClusterMemoryTotal, 142 | (req, res) => { 143 | return res.status(200).json(res.locals.promClusterMemoryTotal); 144 | } 145 | ); 146 | 147 | //----------PROMETHEUS NODES----------// 148 | 149 | // Prometheus node CPU usage 150 | k8sRouter.get('/promNodeCpu', promController.promNodeCpu, (req, res) => { 151 | return res.status(200).json(res.locals.promNodeCpu); 152 | }); 153 | 154 | k8sRouter.get('/promAlerts', promController.promAlerts, (req, res) => { 155 | return res.status(200).json(res.locals.promAlerts); 156 | }); 157 | 158 | //----------PROMQL QUERIES----------// 159 | 160 | k8sRouter.get('/allQueries', queryController.allQueries, (req, res) => { 161 | return res.status(200).json(res.locals.allQueries); 162 | }); 163 | 164 | // Memory in bytes for all namespaces 165 | k8sRouter.get( 166 | '/memoryAllNamespaces', 167 | queryController.memoryAllNamespaces, 168 | (req, res) => { 169 | return res.status(200).json(res.locals.memoryAllNamespaces); 170 | } 171 | ); 172 | 173 | // Memory in bytes for all pods 174 | k8sRouter.get('/memoryAllPods', queryController.memoryAllPods, (req, res) => { 175 | return res.status(200).json(res.locals.memoryAllPods); 176 | }); 177 | 178 | // Cluster Network Util Transmitted 179 | k8sRouter.get('/clusterNetRec', queryController.clusterNetRec, (req, res) => { 180 | return res.status(200).json(res.locals.clusterNetRec); 181 | }); 182 | 183 | // Cluster Network Util Transmitted 184 | k8sRouter.get( 185 | '/clusterNetTrans', 186 | queryController.clusterNetTrans, 187 | (req, res) => { 188 | return res.status(200).json(res.locals.clusterNetTrans); 189 | } 190 | ); 191 | 192 | module.exports = k8sRouter; 193 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | // const exp = require('constants'); 2 | const express = require('express'); 3 | const path = require('path'); 4 | const cors = require('cors'); 5 | const k8sRouter = require('./routes/routes'); 6 | 7 | const PORT = 3000; 8 | const app = express(); 9 | 10 | app.use(cors()); 11 | app.use(express.json()); 12 | app.use(express.urlencoded({ extended: true })); 13 | app.use(express.static(path.resolve(__dirname, '../build'))); 14 | 15 | app.use('/api/k8s', k8sRouter); 16 | 17 | app.get('/api', (req, res) => { 18 | return res.status(200).json('HELLO from Neptune BackEnd'); 19 | }); 20 | 21 | app.get('/', (req, res) => { 22 | return res.status(200).json('TESTING POSTMAN'); 23 | }); 24 | 25 | // Global route handler 26 | app.use('*', (req, res) => { 27 | console.log('Page not found.'); 28 | return res.status(404).send('404! Page not found.'); 29 | }); 30 | 31 | // Global error handler 32 | app.use(defaultErrorHandler); 33 | function defaultErrorHandler(err, req, res, next) { 34 | const defaultErr = { 35 | log: 'Global error handler', 36 | status: 500, 37 | message: { 38 | err: 'An error occurred, and this is the global error handler', 39 | }, 40 | }; 41 | const errorObj = Object.assign(defaultErr, err); 42 | console.log(errorObj.log); 43 | return res.status(errorObj.status).send(JSON.stringify(errorObj.message)); 44 | } 45 | 46 | app.listen(PORT, () => { 47 | console.log(`Server listening on port: ${PORT}...`); 48 | }); 49 | 50 | module.exports = app; 51 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 5 | // const WebpackDevServer = require('webpack-dev-server'); 6 | 7 | const config = { 8 | mode: process.env.NODE_ENV, 9 | entry: './client/index.js', 10 | output: { 11 | path: path.resolve(__dirname, 'build'), 12 | filename: 'bundle.js', 13 | publicPath: '/', 14 | clean: true, 15 | }, 16 | plugins: [ 17 | new HtmlWebpackPlugin({ 18 | template: path.join(__dirname, 'index.html'), 19 | }), 20 | new MiniCssExtractPlugin(), 21 | ], 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.(js|jsx)$/, 26 | use: { 27 | loader: 'babel-loader', 28 | options: { 29 | presets: ['@babel/preset-env', '@babel/preset-react'], 30 | plugins: [], 31 | }, 32 | }, 33 | exclude: /node_modules/, 34 | }, 35 | // { 36 | // test: /\.(ts|tsx)$/, 37 | // use: { 38 | // loader: 'ts-loader', 39 | // }, 40 | // exclude: /node_modules/, 41 | // }, 42 | // { 43 | // test: /\.css$/, 44 | // use: [MiniCssExtractPlugin.loader, "css-loader"], 45 | // }, 46 | { 47 | test: /\.css$/i, 48 | exclude: /node_modules/, 49 | use: [MiniCssExtractPlugin.loader, 'css-loader'], 50 | // check the webpack 51 | // Dockerize diff container 52 | }, 53 | { 54 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 55 | type: 'asset/resource', 56 | }, 57 | ], 58 | }, 59 | 60 | devServer: { 61 | host: 'localhost', 62 | port: 8080, 63 | hot: true, 64 | open: true, 65 | compress: true, 66 | historyApiFallback: true, 67 | 68 | static: { 69 | directory: path.join(__dirname, 'build'), 70 | publicPath: '/', 71 | }, 72 | 73 | headers: { 'Access-Control-Allow-Origin': '*' }, 74 | 75 | proxy: { 76 | '/api/**': { 77 | target: 'http://localhost:3000', 78 | secure: false, 79 | }, 80 | }, 81 | watchFiles: ['client'], 82 | }, 83 | resolve: { extensions: ['.js', '.jsx'] }, 84 | }; 85 | 86 | module.exports = config; 87 | --------------------------------------------------------------------------------