├── client ├── components │ ├── logo.png │ ├── oslabs.png │ ├── fullLogo.png │ ├── logotitle.png │ ├── background.png │ ├── Layout.jsx │ ├── Home.jsx │ ├── dashboard │ │ ├── SideBar.jsx │ │ ├── SideBarPods.jsx │ │ ├── widgets │ │ │ ├── SmallWidget.jsx │ │ │ ├── LineGraph.jsx │ │ │ ├── Graphs.jsx │ │ │ └── SampleData.js │ │ ├── SideBarPods2.jsx │ │ ├── PodDashboard.jsx │ │ ├── NodeDashboard.jsx │ │ └── ClusterDashboard.jsx │ ├── Navbar.jsx │ ├── NavbarDash.jsx │ ├── ClusterCircle.jsx │ ├── SignUp.jsx │ ├── LoginForm.jsx │ └── AddCluster.jsx ├── index.js ├── index.html ├── package.json ├── App.jsx └── styles.css ├── config └── kube-config.js ├── .gitignore ├── server ├── routes │ ├── userRoute.js │ └── metricRoute.js ├── server.js ├── models │ └── userModel.js ├── controllers │ ├── userController.js │ ├── eksController.js │ └── cloudwatchController.js └── package.json ├── webpack.config.js ├── package.json └── README.md /client/components/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kubilyze/HEAD/client/components/logo.png -------------------------------------------------------------------------------- /client/components/oslabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kubilyze/HEAD/client/components/oslabs.png -------------------------------------------------------------------------------- /client/components/fullLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kubilyze/HEAD/client/components/fullLogo.png -------------------------------------------------------------------------------- /client/components/logotitle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kubilyze/HEAD/client/components/logotitle.png -------------------------------------------------------------------------------- /client/components/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kubilyze/HEAD/client/components/background.png -------------------------------------------------------------------------------- /config/kube-config.js: -------------------------------------------------------------------------------- 1 | const k8s = require('@kubernetes/client-node'); 2 | 3 | const kc = new k8s.KubeConfig(); 4 | kc.loadFromDefault(); 5 | module.exports = kc; 6 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | //Entry point for application. 2 | import './styles.css'; 3 | import React from 'react'; 4 | import { createRoot } from 'react-dom/client'; 5 | import App from './App.jsx'; 6 | 7 | //Hangs React app off of #root in index.html 8 | const root = createRoot(document.getElementById('root')); 9 | 10 | //Renders all of app on root 11 | root.render( 12 | 13 | ); 14 | 15 | -------------------------------------------------------------------------------- /client/components/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Outlet } from 'react-router-dom'; 3 | import Navbar from '../components/Navbar'; 4 | 5 | const Layout = () => { 6 | return ( 7 |
8 | {/* Navbar included here */} 9 | {/* The Outlet renders the matched child route component */} 10 |
11 | ); 12 | }; 13 | 14 | export default Layout; 15 | -------------------------------------------------------------------------------- /client/components/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Navbar from "./Navbar.jsx"; 3 | 4 | export default function Home() { 5 | //Returning homepage background image 6 | return ( 7 | <> 8 | 9 |
10 |
11 | Logo 12 |
13 |
14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | Kubilyze 11 | 12 | 13 |
14 |
15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | #General 5 | .env 6 | #aws deployment 7 | *.zip 8 | 9 | #jest coverage 10 | .vscode/ 11 | coverage/ 12 | 13 | #webpack output 14 | dist/ 15 | 16 | #logs 17 | logs 18 | *.log 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | .npm 24 | lib-cov 25 | 26 | # Runtime data 27 | pids 28 | *.pid 29 | *.seed 30 | *.pid.lock 31 | 32 | #secrets 33 | confidential.js 34 | *.env 35 | 36 | .DS_Store 37 | 38 | package-lock.json -------------------------------------------------------------------------------- /server/routes/userRoute.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const userRouter = express.Router() 3 | const userController = require('../controllers/userController.js') 4 | 5 | 6 | 7 | userRouter.post('/signup', userController.signUp, (req, res)=> { 8 | res.status(200).json(res.locals.user) 9 | }) 10 | 11 | userRouter.post('/signin', userController.signIn, (req, res)=> { 12 | res.status(200).json(res.locals.user) 13 | }) 14 | 15 | userRouter.post('/credentials', userController.addCredentials, (req, res)=> { 16 | res.status(200).json(res.locals.user) 17 | }) 18 | 19 | 20 | 21 | 22 | 23 | module.exports = userRouter -------------------------------------------------------------------------------- /client/components/dashboard/SideBar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useNavigate} from "react-router-dom"; 3 | 4 | export default function SideBar({ clusterName }) { 5 | const navigate = useNavigate() 6 | return ( 7 |
8 |
9 |

navigate('/selectcluster')}>{clusterName}

10 |
11 | 15 |
16 | Node 1 17 | Node 2 18 | 19 |
20 |
21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /client/components/dashboard/SideBarPods.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function SideBarPods({ clusterName }) { 4 | return ( 5 |
6 |
7 | Cluster {clusterName} 8 | Node 2 9 |
10 | 14 |
15 | Pod 1 16 | Pod 2 17 | Pod 3 18 | Pod 4 19 | Pod 5 20 | Pod 6 21 | Pod 7 22 |
23 |
24 |
25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /client/components/dashboard/widgets/SmallWidget.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | 3 | export const SmallWidget = ({ type, metric }) => { 4 | let daysRunning; 5 | if(type==='Created:'){ 6 | //Reformat dates in XX/XX/XX format 7 | let currDate = new Date(metric); 8 | let today = new Date(); 9 | const yyyy = currDate.getFullYear(); 10 | let yy = yyyy.toString().slice(2); 11 | let mm = currDate.getMonth() + 1; // Months start at 0! 12 | let dd = currDate.getDate(); 13 | if (dd < 10) dd = '0' + dd; 14 | if (mm < 10) mm = '0' + mm; 15 | let date = mm + '/' + dd + '/' + yy; 16 | let diff = Math.floor((today - currDate) / (1000*60*60*24)); 17 | daysRunning = " " + diff + " days ago"; 18 | metric=date; 19 | } ; 20 | //function to process date 21 | return ( 22 | <> 23 |
24 | 25 |

{type}

26 |

{metric}

27 | {daysRunning &&

{daysRunning}

} 28 | 29 |
30 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /server/routes/metricRoute.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const eks = require('../controllers/eksController'); 3 | const cloudwatchController = require('../controllers/cloudwatchController'); 4 | const metricsRouter = express.Router(); 5 | 6 | // Cluster Metrics Slide : from EKS Controllers 7 | metricsRouter.post('/clusters',eks.describeClusters, eks.describeNodes, (req, res) => { 8 | res.status(200).json({ 9 | clusters: res.locals.clusterInfo, 10 | nodes: res.locals.nodeGroupsDetails, 11 | }); 12 | } 13 | ); 14 | 15 | // Node Metrics Slide : from Cloudwatch Controllers 16 | metricsRouter.post('/metrics/:clustername/:instanceId/:nodeName/:startdate', cloudwatchController.getNodeMetrics, (req, res, next) => { 17 | const metrics = res.locals.metrics; 18 | res.status(200).json(metrics); 19 | }); 20 | //create route based on nodeID 21 | 22 | metricsRouter.post('/metrics/:clustername/allpods', cloudwatchController.getAllPodMetrics, (req, res, next) => { 23 | const metrics = res.locals.metrics; 24 | res.status(200).json(metrics); 25 | }); 26 | 27 | 28 | module.exports = metricsRouter; 29 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | //Boilerplate server file 2 | const path = require('path'); 3 | const cors = require('cors'); 4 | 5 | require('dotenv').config(); 6 | const express = require('express'); 7 | const http = require('http') 8 | const Sever = require("socket.io").Server; 9 | const metricRouter = require('./routes/metricRoute'); 10 | const userRouter = require('./routes/userRoute.js') 11 | 12 | const app = express(); 13 | 14 | app.use(cors()); 15 | app.use(express.json()); 16 | 17 | //Serve files 18 | app.use(express.static(path.resolve(__dirname, '../dist'))); 19 | 20 | app.use('/api', metricRouter); 21 | app.use('/user', userRouter); 22 | 23 | //Glolbal error handler 24 | app.use((err, req, res, next) => { 25 | const defaultErr = { 26 | log: 'Express error handler caught unknown middleware error', 27 | status: 500, 28 | message: { err: 'An error occurred' }, 29 | }; 30 | const errorObj = Object.assign({}, defaultErr, err); 31 | console.log(errorObj.log); 32 | return res.status(errorObj.status).json(errorObj.message); 33 | }); 34 | 35 | const PORT = process.env.PORT || 3000 36 | module.exports = app.listen(PORT, () => 37 | console.log(`Listening on port ${PORT}`) 38 | ); 39 | -------------------------------------------------------------------------------- /client/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import "../styles.css"; 4 | import { Link } from "react-router-dom"; 5 | 6 | export default function Navbar() { 7 | const navigate = useNavigate(); 8 | 9 | const handleLoginClick = () => { 10 | navigate("/login"); 11 | }; 12 | 13 | return ( 14 | <> 15 | 37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /client/components/NavbarDash.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import "../styles.css"; 4 | import { Link } from "react-router-dom"; 5 | import { set } from "mongoose"; 6 | 7 | export default function NavbarDash({ username, setClusterName, setNodes, setCluster }) { 8 | const navigate = useNavigate(); 9 | 10 | const handleLogOutClick = (e) => { 11 | if(e.target.alt) { 12 | setClusterName('') 13 | setNodes('') 14 | setCluster('') 15 | navigate('/') 16 | } 17 | else{ 18 | setClusterName('') 19 | setNodes('') 20 | setCluster('') 21 | navigate("/login"); 22 | } 23 | }; 24 | return ( 25 | <> 26 | 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /server/models/userModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | require('dotenv').config(); 3 | 4 | mongoose.connect(process.env.MONGO_URI) 5 | const db = mongoose.connection; 6 | db.once('connected', ()=> console.log('MongoDb connected')) 7 | const bcrypt = require('bcryptjs') 8 | const SALT_WORK_FACTOR = 10 9 | 10 | /* Creating the schema for you model. This schema shows the proper and required fields 11 | that are required to create a new document */ 12 | const userSchema = new mongoose.Schema({ 13 | username: {type: String, required: true, unique: true}, 14 | password: {type: String, required: true}, 15 | accesskey: String, 16 | secretkey: String, 17 | sessiontoken: String, 18 | region: String 19 | }) 20 | 21 | /* .pre method with 'save' as the first argumemt, allows this function to run whenever 22 | you call db.create(), allowing the password you send to the database to be hashed before it is sent. 23 | */ 24 | userSchema.pre('save', async function (next) { 25 | try{ 26 | const hash = await bcrypt.hash(this.password, SALT_WORK_FACTOR) 27 | this.password = hash; 28 | next(); 29 | } 30 | catch(error) { 31 | console.log(error) 32 | return next({log: error}) 33 | } 34 | }) 35 | 36 | 37 | 38 | // return a model instance that adheres to the schema declared above 39 | // this is the model we use to make queries 40 | module.exports = mongoose.model('users', userSchema) -------------------------------------------------------------------------------- /client/components/dashboard/SideBarPods2.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | // import { useNavigate } from "react-router-dom"; 3 | 4 | export default function SideBarPods({ clusterName, nodeNumber }) { 5 | // const navigate = useNavigate(); 6 | // const handleLoginClick = (nodeIndex) => { 7 | // navigate("/nodedashboard"); 8 | // }; 9 | 10 | // const nodes = Array.from({ length: nodeNumber }, (_, i) => i + 1); 11 | 12 | return ( 13 |
14 |
15 | Cluster first-cluster 16 | Node 17 |
18 |
19 | {/* Node 2 */} 20 | Node 1 21 | Node 2 22 |
23 | 27 |
28 | {/* Node 2 */} 29 | Pod 1 30 | Pod 2 31 | Pod 3 32 | Pod 4 33 | Pod 5 34 | Pod 6 35 | Pod 7 36 | Pod 8 37 | 38 |
39 |
40 |
41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /client/components/dashboard/PodDashboard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import {Graphs} from './widgets/Graphs.jsx'; 3 | import SideBarPods2 from './SideBarPods2.jsx'; 4 | import NavbarDash from "../NavbarDash.jsx"; 5 | import {SmallWidget} from './widgets/SmallWidget.jsx'; 6 | 7 | export default function PodDashboard({ 8 | username, 9 | clusterName, 10 | setClusterName, 11 | setCluster, 12 | nodes, 13 | setNodes, 14 | selectedNode, 15 | pods, 16 | selectedPod, 17 | podData 18 | }) { 19 | 20 | //Rendered elements to be returned 21 | return ( 22 | <> 23 | 24 |
25 | 26 |
27 |
28 |

Pod Dashboard

29 |

Cluster 1: {" "+ clusterName}

30 |

Node {(selectedNode + 1) + ":" + " "+ nodes[selectedNode].name}

31 |

Pods {" "+ pods[selectedPod].name}

32 |
33 |
34 | 35 |
36 |
37 | {/* 38 | 39 | */} 40 |
41 |
42 |
43 | 44 | ); 45 | } -------------------------------------------------------------------------------- /client/components/dashboard/widgets/LineGraph.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, Filler} from 'chart.js'; 3 | import {Line} from 'react-chartjs-2'; 4 | 5 | ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, Filler); 6 | export const LineGraph = ({xData, yData, type}) => { 7 | 8 | //Chart JS data object to be passed as props 9 | const data = { 10 | labels: xData, 11 | datasets: [ 12 | { 13 | label: type, 14 | data: yData, 15 | backgroundColor:'hsl(258, 99%, 95%)', 16 | fill: true, 17 | 18 | } 19 | ] 20 | }; 21 | 22 | //Chart JS propsobject to be passed as props 23 | const options = { 24 | responsive: true, 25 | plugins: { 26 | legend: { 27 | position: "bottom" 28 | }, 29 | title:{ 30 | display: true, 31 | text: type 32 | }, 33 | }, 34 | elements:{ 35 | line: { 36 | borderJoinStyle:'round', 37 | borderColor:'hsl(258, 99%, 47%)', 38 | }, 39 | point: { 40 | pointStyle: false, 41 | } 42 | }, 43 | scales:{ 44 | x: { 45 | display: true, 46 | title: { 47 | display: true, 48 | text: 'Past 7 Days' 49 | }, 50 | drawTicks: true, 51 | ticks:{ 52 | autoSkip: false, 53 | // maxTicksLimit:7 54 | }, 55 | grid: { 56 | drawTicks: false, 57 | } 58 | }, 59 | y: { 60 | // min:0.0745, 61 | // max:0.0775, 62 | display: true, 63 | title: { 64 | display: true, 65 | text: 'Utilization (%)' 66 | }, 67 | } 68 | } 69 | } 70 | 71 | return ( 72 | <> 73 | 74 | 75 | ); 76 | }; 77 | 78 | 79 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './client/index.js', 7 | output: { 8 | path: path.resolve(__dirname, 'dist'), //this is the path that cannot resolve 9 | filename: 'bundle.js', 10 | }, 11 | mode: process.env.NODE_ENV, 12 | 13 | plugins: [ 14 | new HtmlWebpackPlugin({ 15 | template: path.resolve(__dirname, '/client/index.html') 16 | }), 17 | new MiniCssExtractPlugin({ filename: 'style.css' }), 18 | ], 19 | 20 | devServer: { 21 | static: { 22 | directory: path.join(__dirname, '/dist'), 23 | publicPath: '/', 24 | }, 25 | proxy: [ 26 | { 27 | context: ['/plant/'], 28 | target: 'http://localhost:3000', 29 | }, 30 | ], 31 | compress: true, 32 | port: 8080, 33 | historyApiFallback: true, 34 | }, 35 | 36 | module: { 37 | //where our loaders will go (loaders are plugins to translate different languages into css js and html) 38 | rules: [ 39 | { 40 | test: /\.jsx?$/, 41 | exclude: /node_modules/, 42 | use: { 43 | loader: 'babel-loader', 44 | options: { 45 | presets: [ 46 | ['@babel/preset-env', { targets: 'defaults' }], 47 | ['@babel/preset-react'], 48 | ], 49 | plugins: [ 50 | '@babel/plugin-transform-react-jsx', 51 | '@babel/plugin-syntax-jsx', 52 | ], 53 | }, 54 | }, 55 | }, 56 | { 57 | test: /\.css$/i, 58 | use: [ 59 | MiniCssExtractPlugin.loader, 60 | // 'style-loader', 61 | 'css-loader', 62 | 'postcss-loader', 63 | ], 64 | }, 65 | { 66 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i, 67 | type: 'asset', 68 | }, 69 | ], 70 | }, 71 | }; 72 | -------------------------------------------------------------------------------- /client/components/ClusterCircle.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useNavigate, useLocation } from "react-router-dom"; 3 | import NavbarDash from "./NavbarDash.jsx"; 4 | 5 | export const ClusterCircle = ({ 6 | username, 7 | clusterName, 8 | setClusterName, 9 | setCluster, 10 | setNodes, 11 | }) => { 12 | //Routing upon button click 13 | const navigate = useNavigate(); 14 | const handleLoginClick = () => { 15 | navigate("/clusterdashboard"); 16 | }; 17 | 18 | //Upon full page load, fetch cluster info and node identities for rendering on next page, PodDashboard 19 | useEffect(() => { 20 | fetch("https://kubilyze-32a4b0d50531.herokuapp.com/api/clusters", { 21 | method: "POST", 22 | headers: { 23 | "Content-Type": "application/json", 24 | }, 25 | body: JSON.stringify({username}) 26 | }) 27 | .then((res) => { 28 | if(res.ok) return res.json(); 29 | return res.json() 30 | }) 31 | .then((data) => { 32 | if(data.clusters) { 33 | setClusterName(data.clusters[0].name); 34 | setCluster(data.clusters); 35 | setNodes(data.nodes[0].nodes); 36 | } 37 | else if(data === 'Token Expired') { 38 | navigate('/addcluster') 39 | alert('Credentials Expired, Please Re-enter') 40 | } 41 | else { 42 | navigate('/addcluster') 43 | alert('Incorrect Credentials') 44 | } 45 | }) 46 | .catch((err) => console.log("err:", err)); 47 | }, []); 48 | 49 | 50 | //Rendered elements to be returned 51 | return ( 52 | <> 53 | 54 |

Select your cluster below to view your metrics dashboard

55 |
56 | 60 |
61 | 62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kubilyze", 3 | "version": "1.0.0", 4 | "description": "Visualize your EKS kubernetes cluster", 5 | "private": true, 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "NODE_ENV=production webpack", 9 | "build-dev": "webpack --watch", 10 | "start": "node server/server.js", 11 | "dev": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --hot --color\" \"nodemon ./server/server.js\"" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+ssh://git@github.com/oslabs-beta/Kubilyze.git" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/oslabs-beta/Kubilyze" 21 | }, 22 | "homepage": "https://github.com/oslabs-beta/Kubilyze", 23 | "dependencies": { 24 | "@kubernetes/client-node": "^0.21.0", 25 | "@reduxjs/toolkit": "^2.2.3", 26 | "aws-sdk": "^2.1632.0", 27 | "bcryptjs": "^2.4.3", 28 | "chart.js": "^4.4.3", 29 | "cors": "^2.8.5", 30 | "dotenv": "^16.4.5", 31 | "express": "^4.19.2", 32 | "mongodb": "^6.6.2", 33 | "mongoose": "^8.3.2", 34 | "pg": "^8.11.5", 35 | "react": "^18.3.1", 36 | "react-chartjs-2": "^5.2.0", 37 | "react-dom": "^18.3.1", 38 | "react-redux": "^9.1.1", 39 | "react-router-dom": "^6.23.0", 40 | "socket.io": "^4.7.5", 41 | "uuid": "^9.0.1" 42 | }, 43 | "devDependencies": { 44 | "@babel/core": "^7.24.4", 45 | "@babel/preset-env": "^7.24.4", 46 | "@babel/preset-react": "^7.24.1", 47 | "@eslint/js": "^9.1.1", 48 | "autoprefixer": "^10.4.19", 49 | "babel-loader": "^9.1.3", 50 | "build": "^0.1.4", 51 | "concurrently": "^8.2.2", 52 | "cross-env": "^7.0.3", 53 | "css-loader": "^7.1.1", 54 | "eslint": "^8.57.0", 55 | "eslint-plugin-react": "^7.34.1", 56 | "globals": "^15.0.0", 57 | "html-webpack-plugin": "^5.6.0", 58 | "mini-css-extract-plugin": "^2.9.0", 59 | "nodemon": "^3.1.0", 60 | "postcss": "^8.4.38", 61 | "postcss-import": "^16.1.0", 62 | "postcss-loader": "^8.1.1", 63 | "style-loader": "^4.0.0", 64 | "webpack": "^5.91.0", 65 | "webpack-cli": "^5.1.4", 66 | "webpack-dev-server": "^5.0.4" 67 | }, 68 | "main": "webpack.config.js", 69 | "keywords": [] 70 | } 71 | -------------------------------------------------------------------------------- /server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const db = require('../models/userModel.js') 2 | const userController = {} 3 | const bcrypt = require('bcryptjs') 4 | 5 | userController.signUp = (req, res, next)=> { 6 | const{username, password} = req.body 7 | db.create({username, password}) 8 | .then((data)=> { 9 | res.locals.user = data 10 | return next(); 11 | }) 12 | .catch((e)=> { 13 | next({log:e}) 14 | }) 15 | 16 | } 17 | // this controller method handles the verification of a user that it sent to the server 18 | userController.signIn = (req, res, next)=> { 19 | const {username, password} = req.body; 20 | db.findOne({username}) // check the database for the username 21 | .then(data=> { 22 | // if the the query comes back with a user, we now compare that users hashed password with the password they inputed and sent to the server 23 | if(data) { 24 | bcrypt.compare(password, data.password) 25 | .then((result)=> { 26 | if(result) { // if this result is truthy, the bcrypt compare method return true, so the user is verifed 27 | res.locals.user = data 28 | return next() 29 | } 30 | // if we reach this line, the bcrypt compare method return false, meaning the password sent to the server is not correct 31 | res.status(400).json('Password is incorrect') 32 | }) 33 | .catch(e => { 34 | return next({log:e}) 35 | })} 36 | // if db.findOne returns null, we hit this else telling us the username that was sent to server does not exist in the database 37 | else{ 38 | res.status(400).json('Username does not exist') 39 | } 40 | }) 41 | .catch((e)=> { 42 | return next({log:e}) 43 | }) 44 | } 45 | 46 | userController.addCredentials = (req, res, next)=> { 47 | const {username, accesskey, secretkey, sessiontoken, region} = req.body 48 | db.findOneAndUpdate({username}, {accesskey, secretkey, sessiontoken, region}, {new: true}) 49 | .then((data)=> { 50 | res.locals.user = data 51 | return next() 52 | }) 53 | .catch(e=> { 54 | next({log: e}) 55 | }) 56 | } 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | module.exports = userController -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kubilyze", 3 | "version": "1.0.0", 4 | "description": "Visualize your EKS kubernetes cluster", 5 | "private": true, 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "NODE_ENV=production webpack", 9 | "build-dev": "webpack --watch", 10 | "start": "node server/server.js", 11 | "dev": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --hot --color\" \"nodemon ./server/server.js\"" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+ssh://git@github.com/oslabs-beta/Kubilyze.git" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/oslabs-beta/Kubilyze" 21 | }, 22 | "homepage": "https://github.com/oslabs-beta/Kubilyze", 23 | "dependencies": { 24 | "@kubernetes/client-node": "^0.21.0", 25 | "@reduxjs/toolkit": "^2.2.3", 26 | "aws-sdk": "^2.1632.0", 27 | "bcryptjs": "^2.4.3", 28 | "chart.js": "^4.4.3", 29 | "cors": "^2.8.5", 30 | "dotenv": "^16.4.5", 31 | "express": "^4.19.2", 32 | "mongodb": "^6.6.2", 33 | "mongoose": "^8.3.2", 34 | "pg": "^8.11.5", 35 | "react": "^18.3.1", 36 | "react-chartjs-2": "^5.2.0", 37 | "react-dom": "^18.3.1", 38 | "react-redux": "^9.1.1", 39 | "react-router-dom": "^6.23.0", 40 | "socket.io": "^4.7.5", 41 | "uuid": "^9.0.1" 42 | }, 43 | "devDependencies": { 44 | "@babel/core": "^7.24.4", 45 | "@babel/preset-env": "^7.24.4", 46 | "@babel/preset-react": "^7.24.1", 47 | "@eslint/js": "^9.1.1", 48 | "autoprefixer": "^10.4.19", 49 | "babel-loader": "^9.1.3", 50 | "build": "^0.1.4", 51 | "concurrently": "^8.2.2", 52 | "cross-env": "^7.0.3", 53 | "css-loader": "^7.1.1", 54 | "eslint": "^8.57.0", 55 | "eslint-plugin-react": "^7.34.1", 56 | "globals": "^15.0.0", 57 | "html-webpack-plugin": "^5.6.0", 58 | "mini-css-extract-plugin": "^2.9.0", 59 | "nodemon": "^3.1.0", 60 | "postcss": "^8.4.38", 61 | "postcss-import": "^16.1.0", 62 | "postcss-loader": "^8.1.1", 63 | "style-loader": "^4.0.0", 64 | "webpack": "^5.91.0", 65 | "webpack-cli": "^5.1.4", 66 | "webpack-dev-server": "^5.0.4" 67 | }, 68 | "main": "webpack.config.js", 69 | "keywords": [] 70 | } 71 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kubilyze", 3 | "version": "1.0.0", 4 | "description": "Visualize your EKS kubernetes cluster", 5 | "private": true, 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "NODE_ENV=production webpack && cd ../server && npm install ", 9 | "build-dev": "webpack --watch", 10 | "start-client": "react-scripts start", 11 | "dev": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --hot --color\" \"nodemon ./server/server.js\"" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+ssh://git@github.com/oslabs-beta/Kubilyze.git" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/oslabs-beta/Kubilyze" 21 | }, 22 | "homepage": "https://github.com/oslabs-beta/Kubilyze", 23 | "dependencies": { 24 | "@kubernetes/client-node": "^0.21.0", 25 | "@reduxjs/toolkit": "^2.2.3", 26 | "aws-sdk": "^2.1632.0", 27 | "bcryptjs": "^2.4.3", 28 | "chart.js": "^4.4.3", 29 | "cors": "^2.8.5", 30 | "dotenv": "^16.4.5", 31 | "express": "^4.19.2", 32 | "mongodb": "^6.6.2", 33 | "mongoose": "^8.3.2", 34 | "pg": "^8.11.5", 35 | "react": "^18.3.1", 36 | "react-chartjs-2": "^5.2.0", 37 | "react-dom": "^18.3.1", 38 | "react-redux": "^9.1.1", 39 | "react-router-dom": "^6.23.0", 40 | "uuid": "^9.0.1" 41 | }, 42 | "devDependencies": { 43 | "@babel/core": "^7.24.4", 44 | "@babel/preset-env": "^7.24.4", 45 | "@babel/preset-react": "^7.24.1", 46 | "@eslint/js": "^9.1.1", 47 | "autoprefixer": "^10.4.19", 48 | "babel-loader": "^9.1.3", 49 | "build": "^0.1.4", 50 | "concurrently": "^8.2.2", 51 | "cross-env": "^7.0.3", 52 | "css-loader": "^7.1.1", 53 | "eslint": "^8.57.0", 54 | "eslint-plugin-react": "^7.34.1", 55 | "globals": "^15.0.0", 56 | "html-webpack-plugin": "^5.6.0", 57 | "mini-css-extract-plugin": "^2.9.0", 58 | "nodemon": "^3.1.0", 59 | "postcss": "^8.4.38", 60 | "postcss-import": "^16.1.0", 61 | "postcss-loader": "^8.1.1", 62 | "style-loader": "^4.0.0", 63 | "webpack": "^5.91.0", 64 | "webpack-cli": "^5.1.4", 65 | "webpack-dev-server": "^5.0.4" 66 | }, 67 | "main": "webpack.config.js", 68 | "keywords": [] 69 | } 70 | -------------------------------------------------------------------------------- /client/components/dashboard/widgets/Graphs.jsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from 'react'; 2 | import {LineGraph} from './LineGraph.jsx'; 3 | // import {results} from './SampleData.js'; 4 | 5 | export const Graphs = ({results}) => { 6 | 7 | const data = { 8 | cpu:{xData:[], yData:[]}, 9 | mem:{xData:[], yData:[]} 10 | }; 11 | 12 | if(results){ 13 | //Preprocessing of fetched data to prepare for correct format for chartJS 14 | //Iterate through each metric object in the results array 15 | results.forEach((obj)=>{ 16 | //Check object's metric query label for data type 17 | let metric = ''; 18 | if(obj.Label.includes('cpu')) metric = 'cpu'; 19 | if(obj.Label.includes('memory')) metric = 'mem'; 20 | 21 | //Process array of timestamps, x axis data 22 | const timeArr = obj.Timestamps; 23 | let xData = timeArr.map((el) => { 24 | let currDate = new Date(el); 25 | 26 | //Reformat dates in XX/XX/XX format 27 | const yyyy = currDate.getFullYear(); 28 | let yy = yyyy.toString().slice(2); 29 | let mm = currDate.getMonth() + 1; // Months start at 0! 30 | let dd = currDate.getDate(); 31 | if (dd < 10) dd = '0' + dd; 32 | if (mm < 10) mm = '0' + mm; 33 | let formattedDate = mm + '/' + dd + '/' + yy; 34 | return formattedDate; 35 | }); 36 | 37 | //Show date only once along x axes 38 | let seen = new Set(); 39 | xData.forEach((value, index) => { 40 | if (seen.has(value)) { 41 | xData[index] = ""; 42 | } else { 43 | seen.add(value); 44 | } 45 | }); 46 | xData=xData.toReversed();//flip order to plot older dates first 47 | 48 | //Process array of Values, y axis data 49 | const valArr = obj.Values.toReversed();//flip order to plot older dates first 50 | const yData = valArr.map((el) => el.toFixed(3)) 51 | 52 | //Data object to be passed to LineGraph 53 | data[metric]={ 54 | xData: xData, 55 | yData: yData 56 | }; 57 | }) 58 | } 59 | 60 | return ( 61 | <> 62 |
63 | 64 |
65 |
66 | 67 |
68 | 69 | ); 70 | }; 71 | 72 | 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubilyze 2 | 3 | **Kubilyze** is an all-in-one visualizing and monitoring tool designed for AWS EKS Clusters. It provides real-time metrics that give insight into specific nodes and pods by targeting their instance IDs, from the node level down to individualized pods. 4 | 5 | 6 | Kubilyze Logo 7 | 8 |
9 | React Badge 10 | Node.js Badge 11 | Express.js Badge 12 | AWS Badge 13 |
14 | 15 | ## Features 16 | 17 | ### Real-Time Insights 18 | Monitor the health and performance of your clusters in real-time, ensuring immediate detection and resolution of issues. 19 | 20 | ### Optimized Resource Utilization 21 | Track CPU, memory, and storage usage across nodes and pods, allowing for efficient resource allocation and cost reduction. 22 | 23 | ### Simplified Management 24 | Utilize an intuitive visual interface that simplifies the management of complex EKS clusters, with support for automation and maintenance tasks. Kubylize packages scattered metrics eliminating the necessity for navigating various consoles to view related metrics. 25 | 26 | ### Predictive Analysis 27 | Leverage historical data and trends to predict future issues and take proactive measures, ensuring high availability and reliability. 28 | 29 | ### Seamless AWS Integration 30 | Integrate effortlessly with other AWS services like CloudWatch for comprehensive monitoring, alerting, and cross-service visibility. 31 | 32 | Kubilyze empowers developers and operations teams to effectively manage and optimize their AWS EKS clusters, enhancing reliability, performance, and cost-efficiency. 33 | 34 | 35 | 36 | ## Table of Contents 37 | 38 | - [Installation](#installation) 39 | - [Usage](#usage) 40 | - [Features](#features) 41 | - [Contributing](#contributing) 42 | - [License](#license) 43 | - [Contact](#contact) 44 | 45 | ## Installation 46 | 47 | Step-by-step instructions on how to set up and install your project. 48 | 49 | ```bash 50 | # Example commands 51 | git clone https://github.com/yourusername/yourproject.git 52 | cd yourproject 53 | npm install 54 | -------------------------------------------------------------------------------- /client/components/SignUp.jsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import Navbar from "./Navbar.jsx" 4 | 5 | export default function SignUp({setUsername}) { 6 | const navigate = useNavigate(); 7 | const [userInput, setUserInput] = useState({username: '', password: '', confirm: ''}) 8 | 9 | // this handle function handles all input fileds. 10 | const handleUserInput = (e)=> { 11 | setUserInput({...userInput, [e.target.name]: e.target.value}) 12 | } 13 | 14 | const handleSignUpClick = () => { 15 | // we are checking if the two inputs match each other, for password accuracy before we send a fetch request 16 | if(userInput.password === userInput.confirm){ 17 | fetch('https://kubilyze-32a4b0d50531.herokuapp.com/user/signup', { 18 | method: "POST", 19 | headers: { 20 | 'Content-Type': 'application/json' 21 | }, 22 | body: JSON.stringify(userInput) 23 | }) 24 | .then((res)=> { 25 | // if response status is ok we know we can continue to parse the response body with .json() 26 | if(res.ok) return res.json() 27 | // console.log('Username is taken') 28 | // if we hit this line, an alert will be displayed on the sceen telling the client the username entered is taken 29 | alert('Username is taken') 30 | }) 31 | .then((data)=> { 32 | // if data exist, this means the response body was parsed from json to javascript and this data is the user from the server 33 | if(data){ 34 | setUsername(data.username) 35 | navigate("/AddCluster"); 36 | } 37 | // resetting the input fields 38 | setUserInput({username: '', password: '', confirm: ''}) 39 | }) 40 | .catch((e)=> { 41 | console.log(e) 42 | }) 43 | 44 | } 45 | // if the two input passwords do not match, we do not send the request and alert the client 46 | else{ 47 | alert("Password's don't match") 48 | // resetting input fileds 49 | setUserInput({username: '', password: '', confirm: ''}) 50 | } 51 | 52 | }; 53 | 54 | 55 | 56 | 57 | 58 | return ( 59 | <> 60 | 61 |
62 |
63 |
64 |

Create your Account

65 |
66 | 67 |
68 |
69 | 70 |
71 |
72 | 73 |
74 |
75 | 76 |
77 |
78 |
79 |
80 | 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /client/components/dashboard/NodeDashboard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import {Graphs} from './widgets/Graphs.jsx'; 3 | import { useNavigate } from "react-router-dom"; 4 | import SideBarPods from './SideBarPods.jsx'; 5 | import NavbarDash from "../NavbarDash.jsx"; 6 | import {SmallWidget} from './widgets/SmallWidget.jsx'; 7 | 8 | export default function NodeDashboard({ 9 | username, 10 | clusterName, 11 | nodes, 12 | selectedNode, 13 | nodeData, 14 | pods, 15 | setClusterName, 16 | setCluster, 17 | setNodes, 18 | setSelectedPod, 19 | setPodData 20 | }) { 21 | //Routing upon button click 22 | const navigate = useNavigate(); 23 | const handleLoginClick = (index) => { 24 | setSelectedPod(index); 25 | //Upon click, fetch podData for graphs for rendering on next page, PodDashboard 26 | fetch(`https://kubilyze-32a4b0d50531.herokuapp.com/api/metrics/${clusterName}/allpods`, { 27 | method: "POST", 28 | headers: { 29 | "Content-Type": "application/json", 30 | }, 31 | body: JSON.stringify({username}) 32 | }) 33 | .then((res) => { 34 | return res.json(); 35 | }) 36 | .then((data) => { 37 | setPodData(data); 38 | navigate("/poddashboard"); 39 | }) 40 | .catch((err) => console.log("err:", err)); 41 | }; 42 | 43 | //Pod array to map from 44 | const podNums = Array.from({length: pods.length}, (_, i) => i + 1) 45 | 46 | //Rendered elements to be returned 47 | return ( 48 | <> 49 | 50 |
51 | 52 |
53 |
54 |

Node Dashboard

55 |

Cluster 1: {" "+ clusterName}

56 |

Node {(selectedNode + 1) + ":" + " "+ nodes[selectedNode].name}

57 |
58 |
59 | 60 |
61 |
62 | 63 | 64 | 65 |
66 |
67 |

Node Pods

68 |
69 | {podNums.map((button, index) => ( 70 | 74 | ))} 75 |
76 |
77 |
78 |
79 | 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /client/components/dashboard/ClusterDashboard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useNavigate } from "react-router-dom"; 3 | import SideBar from './SideBar.jsx'; 4 | import {SmallWidget} from './widgets/SmallWidget.jsx'; 5 | import NavbarDash from "../NavbarDash.jsx"; 6 | 7 | export default function ClusterDashboard({ 8 | username, 9 | clusterName, 10 | cluster, 11 | nodes, 12 | setCluster, 13 | setClusterName, 14 | setNodes, 15 | setSelectedNode, 16 | setNodeData, 17 | setPods, 18 | }) { 19 | 20 | const podGenerator = (num) => { 21 | const arr =[]; 22 | for( let i =0; i < num; i++){ 23 | arr.push({ 24 | name: " " 25 | }) 26 | } 27 | return arr; 28 | }; 29 | 30 | //Routing upon button click 31 | const navigate = useNavigate(); 32 | const handleLoginClick = (index) => { 33 | setSelectedNode(index); 34 | 35 | //->ToDo: backend edit api request to get podData 36 | //Upon click, fetch nodeData for graphs and pod identities for rendering on next page, NodeDashboard 37 | fetch(`https://kubilyze-32a4b0d50531.herokuapp.com/api/metrics/${clusterName}/${nodes[index].instanceId}/${nodes[index].name}/${cluster[0].createdAt}`, { 38 | method: "POST", 39 | headers: { 40 | "Content-Type": "application/json", 41 | }, 42 | body: JSON.stringify({username}) 43 | }) 44 | .then((res) => { 45 | return res.json(); 46 | }) 47 | .then((data) => { 48 | setNodeData(data); 49 | setPods(podGenerator(data[2].Values[0])); 50 | navigate("/nodedashboard"); 51 | }) 52 | .catch((err) => console.log("err:", err)); 53 | }; 54 | 55 | //Node array to map from 56 | const nodeNums = Array.from({length: nodes.length}, (_, i) => i + 1) 57 | 58 | //Rendered elements to be returned 59 | return ( 60 | <> 61 | 62 |
63 | 64 |
65 |
66 |

Cluster Dashboard

67 |

Cluster 1: {" "+ clusterName}

68 |
69 |
70 | 71 | 72 | 73 |
74 |
75 |

Cluster Nodes

76 |
77 | {nodeNums.map((button, index) => ( 78 | 82 | ))} 83 |
84 |
85 |
86 |
87 | 88 | ); 89 | }; 90 | -------------------------------------------------------------------------------- /client/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Home from "./components/Home.jsx"; 3 | import LoginForm from "./components/LoginForm.jsx"; 4 | import SignUp from "./components/SignUp.jsx"; 5 | import { Routes, Route, BrowserRouter, Link } from "react-router-dom"; 6 | import AddCluster from "./components/AddCluster.jsx"; 7 | import { ClusterCircle } from "./components/ClusterCircle.jsx"; 8 | import ClusterDashboard from "./components/dashboard/ClusterDashboard.jsx"; 9 | import NodeDashboard from "./components/dashboard/NodeDashboard.jsx"; 10 | import PodDashboard from "./components/dashboard/PodDashboard.jsx"; 11 | 12 | // 13 | const App = () => { 14 | const [username, setUsername] = useState(''); 15 | //State related to Clusters 16 | const [clusterName, setClusterName] = useState(""); 17 | const [cluster, setCluster] = useState([]); 18 | //State related to Nodes 19 | const [nodes, setNodes] = useState([]); 20 | const [selectedNode, setSelectedNode] = useState(0); 21 | const [nodeData, setNodeData] = useState(''); 22 | //State related to Pods 23 | const [pods, setPods] = useState([]); 24 | const [selectedPod, setSelectedPod] = useState(0); 25 | const [podData, setPodData] = useState(''); 26 | 27 | //Returning all of our routes for our application 28 | //At each route rendering components 29 | //At each component passing down state as props 30 | return ( 31 | 32 | 33 | } /> 34 | } /> 35 | } /> 36 | } /> 37 | 47 | } 48 | /> 49 | 64 | } 65 | /> 66 | 82 | } 83 | /> 84 | 99 | } 100 | /> 101 | 102 | 103 | ); 104 | }; 105 | 106 | export default App; 107 | 108 | -------------------------------------------------------------------------------- /server/controllers/eksController.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | const db = require('../models/userModel'); 3 | const e = require('express'); 4 | 5 | const eksController = {}; 6 | 7 | eksController.describeClusters = async (req, res, next) => { 8 | try { 9 | const {username} = req.body 10 | const user = await db.findOne({username}) 11 | AWS.config.update({ 12 | region: user.region, 13 | accessKeyId: user.accesskey, 14 | secretAccessKey: user.secretkey, 15 | sessionToken: user.sessiontoken 16 | }); 17 | res.locals.AWS = AWS 18 | const eks = new AWS.EKS() 19 | res.locals.eks = eks 20 | const data = await eks.listClusters().promise(); 21 | const clusterNames = data.clusters; 22 | 23 | // Fetch detailed information for each cluster 24 | const clusterDetailsPromises = clusterNames.map(async (clusterName) => { 25 | const clusterData = await eks 26 | .describeCluster({ name: clusterName }) 27 | .promise(); 28 | const nodeGroupsData = await eks 29 | .listNodegroups({ clusterName }) 30 | .promise(); 31 | const nodeGroupNames = nodeGroupsData.nodegroups; 32 | 33 | // Extract pertinent details 34 | return { 35 | name: clusterData.cluster.name, 36 | status: clusterData.cluster.status, 37 | version: clusterData.cluster.version, 38 | createdAt: clusterData.cluster.createdAt, 39 | tags: clusterData.cluster.tags, 40 | health: clusterData.cluster.health, 41 | nodeGroupNames: nodeGroupNames, 42 | }; 43 | }); 44 | 45 | // Wait for all promises to resolve 46 | const clustersInfo = await Promise.all(clusterDetailsPromises); 47 | 48 | // Set the locals and proceed 49 | res.locals.clusterInfo = clustersInfo; 50 | next(); 51 | } catch (err) { 52 | if(err.message === 'The security token included in the request is expired') { 53 | res.status(400).json('Token Expired')} 54 | else { 55 | next({log:err}) 56 | } 57 | } 58 | }; 59 | 60 | eksController.describeNodes = async (req, res, next) => { 61 | try { 62 | const nodeGroupsDetailsPromises = res.locals.clusterInfo.flatMap( 63 | (cluster) => 64 | cluster.nodeGroupNames.map(async (nodeGroupName) => { 65 | const clusterName = cluster.name; 66 | const nodeGroupData = await res.locals.eks 67 | .describeNodegroup({ clusterName, nodegroupName: nodeGroupName }) 68 | .promise(); 69 | const autoScalingGroupName = 70 | nodeGroupData.nodegroup.resources.autoScalingGroups[0].name; 71 | 72 | // List instances in the Auto Scaling group 73 | const autoscaling = new res.locals.AWS.AutoScaling() 74 | const autoScalingGroupData = await autoscaling 75 | .describeAutoScalingGroups({ 76 | AutoScalingGroupNames: [autoScalingGroupName], 77 | }) 78 | .promise(); 79 | 80 | const instanceIds = 81 | autoScalingGroupData.AutoScalingGroups[0].Instances.map( 82 | (instance) => instance.InstanceId 83 | ); 84 | 85 | // Fetch detailed information for each instance 86 | const ec2 = new res.locals.AWS.EC2() 87 | const instanceData = await ec2 88 | .describeInstances({ 89 | InstanceIds: instanceIds, 90 | }) 91 | .promise(); 92 | 93 | const nodesInfo = instanceData.Reservations.flatMap((reservation) => 94 | reservation.Instances.map((instance) => ({ 95 | instanceId: instance.InstanceId, 96 | name: instance.PrivateDnsName || 'Unnamed', 97 | state: instance.State.Name, 98 | launchTime: instance.LaunchTime, 99 | })) 100 | ); 101 | 102 | return { 103 | clusterName, 104 | nodeGroupName: nodeGroupData.nodegroup.nodegroupName, 105 | nodes: nodesInfo, 106 | }; 107 | }) 108 | ); 109 | 110 | const nodeGroupsDetails = await Promise.all(nodeGroupsDetailsPromises); 111 | 112 | // Store the node group details in res.locals 113 | res.locals.nodeGroupsDetails = nodeGroupsDetails; 114 | next(); 115 | } catch (err) { 116 | console.error('Error fetching node details:', err); 117 | next({ log: err }); // Pass the error to the next middleware 118 | } 119 | }; 120 | 121 | module.exports = eksController; 122 | 123 | //VERIFY : can I get the node name here without having to make another request -------------------------------------------------------------------------------- /client/components/LoginForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { useNavigate } from "react-router-dom"; 4 | import Navbar from "./Navbar.jsx"; 5 | 6 | export default function LoginForm({setUsername}) { 7 | const navigate = useNavigate(); 8 | const [userInput, setUserInput] = useState({username: '', password: ''}); 9 | 10 | // this handle function handles all input fileds. 11 | const handleUserInput = (e)=> { 12 | setUserInput({...userInput, [e.target.name]: e.target.value}) 13 | } 14 | const handleLoginClick = () => { 15 | // console.log(userInput) 16 | fetch('https://kubilyze-32a4b0d50531.herokuapp.com/user/signin', { 17 | method: "POST", 18 | headers: { 19 | 'Content-Type': 'application/json' 20 | }, 21 | body: JSON.stringify(userInput) 22 | }) 23 | .then(async (data)=> { 24 | //checking if response status is ok, if so continue to parse body of response object and return user 25 | if(data.ok) return data.json() 26 | // if response status is not ok, parse body of response for the error 'String' and alert 27 | // it to the screen 28 | const log = await data.json() 29 | console.log(log) 30 | alert(log) 31 | }) 32 | .then((user)=> { 33 | // if user argument exist, then all previous checks passed and user from server 34 | // is passed in and page navigates to '/selectcluster' page 35 | if(user) { 36 | setUsername(user.username) 37 | navigate('/selectcluster') 38 | } 39 | // restting the input fields 40 | setUserInput({username: '', password: ''}) 41 | }) 42 | }; 43 | 44 | return ( 45 | <> 46 | 47 |
48 | 49 |
50 |
51 |

Sign in to Kubilyze

52 |
53 | {/* */} 54 | 55 |
56 |
57 | {/* */} 58 | 59 |
60 |
61 | 62 |
63 |

Don't have an account?

64 |

65 | 66 | Sign Up 67 | 68 |
69 |
70 |
71 | 72 | ); 73 | } 74 | 75 | /*
76 |
77 | 78 | setUsername(e.target.value)} 83 | required 84 | autoFocus 85 | /> 86 |
87 | {username === "" && "Username is required"} 88 |
89 | 90 | 91 | setPassword(e.target.value)} 97 | required 98 | /> 99 |
100 | {password === "" && "Password is required"} 101 |
102 | 103 |
104 | 107 |
*/ 108 | 109 | // const [username, setUsername] = useState(""); 110 | // const [password, setPassword] = useState(""); 111 | 112 | // const submit = async (e) => { 113 | // e.preventDefault(); 114 | // try { 115 | // const res = await fetch("/api/", { 116 | // method: "POST", 117 | // headers: { "Content-Type": "application/json" }, 118 | // body: JSON.stringify({ username, password }), 119 | // }); 120 | // if (!res.ok) throw new Error("Something went wrong!"); 121 | // console.log(await res.json()); 122 | // // Reset form 123 | // setUsername(""); 124 | // setPassword(""); 125 | // } catch (err) { 126 | // console.log("Error: ", err); 127 | // } 128 | // }; 129 | 130 | // const navigate = useNavigate(); 131 | 132 | // const handleClick = () => { 133 | // navigate("/SignUp"); 134 | // }; 135 | 136 | // const handleClick2 = () => { 137 | // navigate("/SignUp"); 138 | // }; 139 | -------------------------------------------------------------------------------- /server/controllers/cloudwatchController.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | const db = require('../models/userModel.js'); 3 | const cloudwatchController = {}; 4 | 5 | cloudwatchController.getNodeMetrics = async (req, res, next) => { 6 | const { clustername, instanceId, nodeName, startdate } = req.params; 7 | let secondsPassed = ((new Date() - new Date(startdate)) / 1000); 8 | let period = Math.round((secondsPassed / 2) / 60) * 60; 9 | 10 | //define params for cloudwatch api metric request 11 | const params = { 12 | MetricDataQueries: [ 13 | { 14 | Id: 'm1', 15 | MetricStat: { 16 | Metric: { 17 | Namespace: 'ContainerInsights', 18 | MetricName: 'node_cpu_utilization', 19 | Dimensions: [ 20 | { Name: 'ClusterName', Value: clustername }, 21 | { Name: 'InstanceId', Value: instanceId }, 22 | { Name: 'NodeName', Value: nodeName }, 23 | ], 24 | }, 25 | Period: 12600, 26 | Stat: 'Average', 27 | }, 28 | ReturnData: true, 29 | }, 30 | { 31 | Id: 'm2', 32 | MetricStat: { 33 | Metric: { 34 | Namespace: 'ContainerInsights', 35 | MetricName: 'node_memory_utilization', 36 | Dimensions: [ 37 | { Name: 'ClusterName', Value: clustername }, 38 | { Name: 'InstanceId', Value: instanceId }, 39 | { Name: 'NodeName', Value: nodeName }, 40 | ], 41 | }, 42 | Period: 12600, 43 | Stat: 'Average', 44 | }, 45 | ReturnData: true, 46 | }, 47 | { 48 | Id: 'm3', 49 | MetricStat: { 50 | Metric: { 51 | Namespace: 'ContainerInsights', 52 | MetricName: 'node_number_of_running_pods', 53 | Dimensions: [ 54 | { Name: 'ClusterName', Value: clustername }, 55 | { Name: 'InstanceId', Value: instanceId }, 56 | { Name: 'NodeName', Value: nodeName }, 57 | ], 58 | }, 59 | Period: period, 60 | Stat: 'Average', 61 | }, 62 | ReturnData: true, 63 | }, 64 | ], 65 | StartTime: new Date(Date.now() - 7 * 24 * 3600 * 1000), //Past 7 days 66 | EndTime: new Date(), // End time now 67 | }; 68 | try { 69 | const {username} = req.body 70 | const user = await db.findOne({username}) 71 | AWS.config.update({ 72 | region: user.region, 73 | accessKeyId: user.accesskey, 74 | secretAccessKey: user.secretkey, 75 | sessionToken: user.sessiontoken 76 | }); 77 | const cloudwatch = new AWS.CloudWatch() 78 | const data = await cloudwatch.getMetricData(params).promise(); 79 | res.locals.metrics = data.MetricDataResults; 80 | return next(); 81 | } catch (err) { 82 | if(err.message === 'The security token included in the request is expired') { 83 | res.status(400).json('Token Expired')} 84 | else { 85 | next({log:err}) 86 | } 87 | } 88 | }; 89 | 90 | cloudwatchController.getAllPodMetrics = async (req, res, next) => { 91 | const {clustername} = req.params; 92 | const params = { 93 | MetricDataQueries: [ 94 | { 95 | Id: 'm1', 96 | MetricStat: { 97 | Metric: { 98 | Namespace: 'ContainerInsights', 99 | MetricName: 'pod_cpu_utilization', 100 | Dimensions: [ 101 | { Name: 'ClusterName', Value: clustername }, 102 | { Name: 'Namespace', Value: 'amazon-cloudwatch' }, 103 | { Name: 'Service', Value: 'cloudwatch-agent' }, 104 | ], 105 | }, 106 | Period: 12600, 107 | Stat: 'Average', 108 | }, 109 | ReturnData: true, 110 | }, 111 | { 112 | Id: 'm2', 113 | MetricStat: { 114 | Metric: { 115 | Namespace: 'ContainerInsights', 116 | MetricName: 'pod_memory_utilization', 117 | Dimensions: [ 118 | { Name: 'ClusterName', Value: clustername }, 119 | { Name: 'Namespace', Value: 'amazon-cloudwatch' }, 120 | { Name: 'Service', Value: 'cloudwatch-agent' }, 121 | ], 122 | }, 123 | Period: 12600, 124 | Stat: 'Average', 125 | }, 126 | ReturnData: true, 127 | }, 128 | ], 129 | StartTime: new Date(Date.now() - 7 * 24 * 3600 * 1000), //Past 7 days 130 | EndTime: new Date(), // End time now 131 | }; 132 | 133 | try { 134 | const {username} = req.body 135 | const user = await db.findOne({username}) 136 | AWS.config.update({ 137 | region: user.region, 138 | accessKeyId: user.accesskey, 139 | secretAccessKey: user.secretkey, 140 | sessionToken: user.sessiontoken 141 | }); 142 | const cloudwatch = new AWS.CloudWatch() 143 | const data = await cloudwatch.getMetricData(params).promise(); 144 | res.locals.metrics = data.MetricDataResults; // Save metrics in res.locals 145 | return next(); 146 | } catch (err) { 147 | if(err.message === 'The security token included in the request is expired') { 148 | res.status(400).json('Token Expired')} 149 | else { 150 | next({log:err}) 151 | } 152 | } 153 | }; 154 | 155 | module.exports = cloudwatchController; 156 | -------------------------------------------------------------------------------- /client/components/AddCluster.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import NavbarDash from "./NavbarDash.jsx"; 4 | 5 | export default function AddCluster({ 6 | username, 7 | setClusterName, 8 | setCluster, 9 | setNodes, 10 | }) { 11 | const [accessInfo, setAccessInfo] = useState({ 12 | accesskey: "", 13 | secretkey: "", 14 | sessiontoken: "", 15 | region: "", 16 | }); 17 | const navigate = useNavigate(); 18 | const [modalOpen, setModalOpen] = useState(false); 19 | 20 | const handleUserInput = (e) => { 21 | setAccessInfo({ ...accessInfo, [e.target.name]: e.target.value }); 22 | }; 23 | 24 | const addCredentials = () => { 25 | 26 | fetch('https://kubilyze-32a4b0d50531.herokuapp.com/user/credentials', { 27 | method: 'POST', 28 | headers: { 29 | "Content-Type": "application/json", 30 | }, 31 | body: JSON.stringify({ ...accessInfo, username }), 32 | }) 33 | .then((response) => { 34 | if (response.ok) return response.json(); 35 | console.log("an error happened in the server"); 36 | }) 37 | .then((user) => { 38 | if (user) { 39 | console.log(user); 40 | navigate("/selectcluster"); 41 | } 42 | setAccessInfo({ accesskey: "", secretkey: "", sessiontoken: "" }); 43 | }) 44 | .catch((e) => console.log(e)); 45 | }; 46 | 47 | const toggleModal = () => { 48 | setModalOpen(!modalOpen); 49 | }; 50 | 51 | return ( 52 | <> 53 | 59 |
60 |
61 |
62 |

Add Your EKS Cluster

63 |
64 | 71 |
72 |
73 | 80 |
81 |
82 | 89 |
90 |
91 | 98 |
99 | 100 |
101 | 102 |
103 |
104 | 105 |
106 |
107 |
108 |
109 | {modalOpen && ( 110 |
111 |
112 | 113 | × 114 | 115 |

How to add your EKS Clusters

116 | 117 |

118 |

119 | Please make sure to follow the steps of the ReadMe closely. You 120 | can find the ReadMe linked in the Docs on the homepage. 121 |

122 |

123 |

124 | Make sure to create a role in your AWS account and have an IAM 125 | user assume that role so that you're inputting those{" "} 126 | temporary 127 | creditionals into the app. 128 |

129 |

130 |

131 | Please note that the credentials have a default time limit of 1 132 | hour unless you indicate otherwise. Your credentials will expire 133 | after that duration and you will need to regenerate tokens. 134 |

135 |
136 |
137 | )} 138 | 139 | ); 140 | } 141 | 142 | // import React, { useState } from "react"; 143 | // import { useNavigate } from "react-router-dom"; 144 | // import NavbarDash from "./NavbarDash.jsx"; 145 | 146 | // // import { useNavigate } from "react-router-dom"; 147 | // // import "../styles.css"; 148 | 149 | // export default function AddCluster({username}) { 150 | // const navigate = useNavigate(); 151 | // const handleLoginClick = () => { 152 | // navigate("/selectcluster"); 153 | // }; 154 | // return ( 155 | // <> 156 | // 157 | //
158 | //
159 | //
160 | //

Add Your EKS Cluster

161 | //
162 | // 163 | //
164 | //
165 | // 166 | //
167 | //
168 | // 169 | //
170 | //
171 | // 172 | //
173 | //
174 | // 175 | //
176 | //
177 | //
178 | //
179 | // 180 | // ); 181 | // } 182 | -------------------------------------------------------------------------------- /client/styles.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); 2 | 3 | 4 | .roboto-thin { 5 | font-family: "Roboto", sans-serif; 6 | font-weight: 100; 7 | font-style: normal; 8 | } 9 | 10 | .roboto-light { 11 | font-family: "Roboto", sans-serif; 12 | font-weight: 300; 13 | font-style: normal; 14 | } 15 | 16 | .roboto-regular { 17 | font-family: "Roboto", sans-serif; 18 | font-weight: 400; 19 | font-style: normal; 20 | } 21 | 22 | .roboto-medium { 23 | font-family: "Roboto", sans-serif; 24 | font-weight: 500; 25 | font-style: normal; 26 | } 27 | 28 | .roboto-bold { 29 | font-family: "Roboto", sans-serif; 30 | font-weight: 700; 31 | font-style: normal; 32 | } 33 | 34 | .roboto-black { 35 | font-family: "Roboto", sans-serif; 36 | font-weight: 900; 37 | font-style: normal; 38 | } 39 | 40 | .roboto-thin-italic { 41 | font-family: "Roboto", sans-serif; 42 | font-weight: 100; 43 | font-style: italic; 44 | } 45 | 46 | .roboto-light-italic { 47 | font-family: "Roboto", sans-serif; 48 | font-weight: 300; 49 | font-style: italic; 50 | } 51 | 52 | .roboto-regular-italic { 53 | font-family: "Roboto", sans-serif; 54 | font-weight: 400; 55 | font-style: italic; 56 | } 57 | 58 | .roboto-medium-italic { 59 | font-family: "Roboto", sans-serif; 60 | font-weight: 500; 61 | font-style: italic; 62 | } 63 | 64 | .roboto-bold-italic { 65 | font-family: "Roboto", sans-serif; 66 | font-weight: 700; 67 | font-style: italic; 68 | } 69 | 70 | .roboto-black-italic { 71 | font-family: "Roboto", sans-serif; 72 | font-weight: 900; 73 | font-style: italic; 74 | } 75 | 76 | 77 | /* Reset some default browser styles */ 78 | * { 79 | margin: 0; 80 | padding: 0; 81 | box-sizing: border-box; 82 | } 83 | 84 | body { 85 | margin: 0; 86 | font-family: "Roboto", sans-serif; 87 | 88 | } 89 | /* ----------HOMEPAGE ----------*/ 90 | #homepage { 91 | background-color: #ECFEFF; 92 | width:100%; 93 | height: 100%; 94 | 95 | } 96 | 97 | /* ----------NAVBAR ----------*/ 98 | .welcome{ 99 | margin-top: 100px; 100 | display: flex; 101 | justify-content: center; 102 | } 103 | 104 | .fullLogo{ 105 | display: flex; 106 | justify-content: center; 107 | background-color: #ECFEFF; 108 | margin-top: 85px;; 109 | } 110 | 111 | .fullLogo img{ 112 | width: 100%; 113 | } 114 | 115 | .logo{ 116 | display: flex; 117 | align-items: left; 118 | justify-content: left; 119 | } 120 | 121 | .logotitle{ 122 | display: flex; 123 | align-items: left; 124 | margin-left: 0px; 125 | } 126 | 127 | .logotitle img{ 128 | height: 20px; 129 | 130 | } 131 | 132 | .logo img { 133 | height: 60px; /*Adjust the height as needed */ 134 | margin-top: 5px; 135 | margin-bottom: 5px; 136 | margin-right: 10px; 137 | 138 | 139 | } 140 | 141 | .tagline{ 142 | display: flex; 143 | justify-content: center; 144 | padding-top: 10px; 145 | padding-bottom: 15px 146 | } 147 | 148 | #navbar { 149 | display: flex; 150 | /* justify-content: space-between; */ 151 | 152 | align-items: center; 153 | background-color: #19648D; /* You can change the background color as needed */ 154 | padding: 20px 50px; 155 | position: fixed; 156 | top: 0; 157 | left: 0; /* Ensure the navbar touches the left edge */ 158 | right: 0; /* Ensure the navbar touches the right edge */ 159 | width: 100%; 160 | box-sizing: border-box; 161 | z-index: 1000; /* Ensures the navbar stays on top */ 162 | height: 90px; 163 | } 164 | 165 | 166 | /* 167 | .nav-container { 168 | display: flex; 169 | flex-grow: 1; 170 | justify-content: space-between; 171 | align-items: center; 172 | } */ 173 | 174 | .rightside{ 175 | display: flex; 176 | align-items: center; 177 | width: 100%; 178 | justify-content: right; 179 | } 180 | 181 | .rightside a{ 182 | color: #efefef; 183 | } 184 | 185 | .nav-links { 186 | display: flex; 187 | gap: 20px; 188 | margin-left: 40px; 189 | 190 | } 191 | 192 | .nav-links a { 193 | color: #efefef; 194 | text-decoration: none; 195 | padding: 7px; 196 | 197 | } 198 | 199 | .nav-links a:hover { 200 | background-color: #e1e1e1; /* Change the hover color as needed */ 201 | color: rgb(48, 48, 48); 202 | border-radius: 4px; 203 | } 204 | 205 | .auth-button button { 206 | background-color: #efefef; /* Change the button color as needed */ 207 | color: rgb(0, 0, 0); 208 | border: none; 209 | padding: 10px 20px; 210 | cursor: pointer; 211 | border-radius: 4px; 212 | /* display: flex; 213 | justify-content: right; */ 214 | } 215 | .auth-button button:hover { 216 | background-color: #cacaca; 217 | } 218 | 219 | #header { 220 | margin-top: 60px; /* Adjust if needed to account for the fixed navbar height */ 221 | padding: 20px; 222 | } 223 | 224 | /* ----------LOGIN PAGE ----------*/ 225 | .entirepage{ 226 | position: fixed; 227 | top: 0; 228 | left: 0; 229 | height: 100%; 230 | width: 100%; 231 | background-color: hsl(0, 0%, 94%); 232 | 233 | } 234 | 235 | #loginform { 236 | background-color: #fcfcfc; 237 | border-radius: 15px; 238 | display: flex; 239 | flex-direction: column; 240 | align-items: center; 241 | width: fit-content; 242 | padding: 60px; 243 | box-shadow: 0 0 10px rgba(56, 56, 56, 0.1); 244 | margin-top: 150px; 245 | } 246 | 247 | /* Input Field Styles */ 248 | #loginform input[type="text"] { 249 | width: 250px; 250 | background-color: #DFE5F6; 251 | border-radius: 5px; 252 | border: none; 253 | padding: 15px; 254 | /* margin-bottom: 15px; */ 255 | } 256 | 257 | .loginContainer { 258 | display: flex; 259 | justify-content: center; 260 | align-items: center; 261 | 262 | } 263 | 264 | .title { 265 | margin-bottom: 40px; 266 | 267 | } 268 | 269 | .formGroup { 270 | margin-bottom: 15px; 271 | } 272 | 273 | #username { 274 | width: 250px; 275 | background-color: #DFE5F6; 276 | border-radius: 5px; 277 | border: none; 278 | padding: 15px; 279 | } 280 | 281 | #password { 282 | width: 250px; 283 | background-color: #DFE5F6; 284 | border-radius: 5px; 285 | border: none; 286 | padding: 15px; 287 | } 288 | 289 | .account { 290 | margin-top: 40px; 291 | margin-bottom: 5px; 292 | } 293 | 294 | /* ----------SIGNUP PAGE ----------*/ 295 | .signup { 296 | margin-top: 5px; 297 | text-decoration: none; 298 | color: #343940; /* Adjust color as needed */ 299 | font-size: small; 300 | border: 1px solid #5d6a7c; 301 | padding: 5px; 302 | border-radius: 5px; 303 | } 304 | 305 | .signup:hover{ 306 | color: #1F87CA; 307 | border: 1px solid #19648D; 308 | 309 | } 310 | 311 | .submit button{ 312 | margin-top: 10px; 313 | background-color: #5d6a7c; /* Change the button color as needed */ 314 | color: white; 315 | border: none; 316 | padding: 10px 20px; 317 | cursor: pointer; 318 | border-radius: 5px; 319 | width: 250px; 320 | } 321 | 322 | .submit button:hover { 323 | background-color: #19648D; /* Change the button color as needed */ 324 | } 325 | 326 | /* ----------METRIC PAGES----------*/ 327 | #page{ 328 | width:100%; 329 | position:absolute; 330 | top:75px; 331 | display: flex; 332 | /* height should ideally based on media query */ 333 | height: auto; 334 | background-color: hsl(210, 5%, 93%); 335 | 336 | } 337 | 338 | /* ----------SIDEBAR ----------*/ 339 | /*sidebar css begins*/ 340 | /* Links inside the sidebar */ 341 | /* Sidebar Styles */ 342 | .sidebar { 343 | background-color: hsl(210, 50%, 97%); 344 | min-width: 20%; 345 | display: flex; 346 | justify-content: center; 347 | padding: 10px; 348 | } 349 | 350 | .sidebarMenu { 351 | overflow: hidden; 352 | font-family: Arial; 353 | display: flex; 354 | flex-direction: column; 355 | width: 75%; 356 | margin-top: 50px; 357 | } 358 | 359 | .sidebarMenu a { 360 | font-size: 16px; 361 | color: rgb(38, 38, 38); 362 | text-align: left; 363 | padding: 14px 16px; 364 | text-decoration: none; 365 | border-radius: 10px; 366 | display: block; /* Ensure the links are block elements */ 367 | } 368 | 369 | .sidebarMenu a:hover { 370 | background-color: #19648C; 371 | color: white; 372 | } 373 | 374 | /* Dropdown Styles */ 375 | .dropdown { 376 | position: relative; /* Ensure the dropdown content is positioned correctly */ 377 | width: 100%; 378 | } 379 | 380 | .dropbtn { 381 | font-size: 16px; 382 | border: none; 383 | outline: none; 384 | color: rgb(0, 0, 0); 385 | padding: 14px 16px; 386 | border-radius: 10px; 387 | background-color: inherit; 388 | font-family: inherit; /* Important for vertical align on mobile phones */ 389 | margin: 0; /* Important for vertical align on mobile phones */ 390 | display: flex; 391 | align-items: center; 392 | justify-content: space-between; 393 | width: 100%; 394 | text-align: left; /* Align text to the left */ 395 | } 396 | 397 | .dropdown-content { 398 | display: none; 399 | position: absolute; 400 | background-color: #f9f9f9; 401 | min-width: 100%; /* Ensure the dropdown matches the width of the button */ 402 | box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); 403 | z-index: 1; 404 | border-radius: 10px; 405 | left: 0; 406 | } 407 | 408 | .dropdown-content a { 409 | color: black; 410 | padding: 12px 16px; 411 | text-decoration: none; 412 | display: block; 413 | text-align: left; 414 | } 415 | 416 | .dropdown-content a:hover { 417 | background-color: #19648C; 418 | color: white; 419 | } 420 | 421 | .dropdown:hover .dropdown-content { 422 | display: block; 423 | } 424 | 425 | .dropdown:hover .dropbtn { 426 | background-color: #19648C; 427 | color: white; 428 | } 429 | 430 | 431 | 432 | /* ----------DASHBOARD ----------*/ 433 | .dashboard{ 434 | /* background-color: hsl(209, 63%, 81%); */ 435 | 436 | width:85%; 437 | margin:20px; 438 | padding: 20px; 439 | /* border-radius: 50px; */ 440 | border-width:0.5px; 441 | /* overflow-y: auto; */ 442 | } 443 | /* .dashboard-title h1{ 444 | margin-bottom:20px; 445 | } */ 446 | .dashboard h2, h4{ 447 | color: hsl(0, 0%, 31%); 448 | font-size: medium; 449 | margin-bottom: 10px; 450 | font-weight: bolder; 451 | } 452 | .dashboard-title { 453 | 454 | display: flex; 455 | align-items: baseline; 456 | } 457 | .dashboard-title h1{ 458 | color: hsl(0, 0%, 20%); 459 | 460 | margin-right: 20px; 461 | 462 | } 463 | .dashboard-title h4{ 464 | margin-right: 20px; 465 | color: hsl(0, 0%, 50%); 466 | } 467 | .dashboard-title h3{ 468 | color: hsl(0, 0%, 31%); 469 | 470 | margin-top: 12px; 471 | } 472 | .widget-container{ 473 | display: grid; 474 | grid-template-columns: 1fr 1fr 1fr; 475 | gap: 20px; 476 | margin: 10px 0; 477 | margin-bottom: 20px ; 478 | } 479 | .widget{ 480 | display:flex; 481 | flex-direction:column; 482 | align-items: center; 483 | justify-content: center; 484 | /* justify-content: space-around; */ 485 | padding:15px; 486 | margin-top: 10px; 487 | border-radius: 5px; 488 | background-color: hsl(0, 0%, 100%); 489 | height: 150px; 490 | border-radius: 10px; 491 | box-shadow: 0 2px 6px 0 hsla(0,0%,0%,0.2); 492 | 493 | } 494 | .clustit{ 495 | display: flex; 496 | justify-content: center; 497 | color: rgb(36, 36, 36); 498 | margin-top: 150px; 499 | } 500 | .nodes-div{ 501 | border-radius: 10px; 502 | background-color: hsl(0, 0%, 100%); 503 | padding:15px; 504 | padding-bottom: 50px ; 505 | box-shadow: 0 2px 6px 0 hsla(0,0%,0%,0.2); 506 | 507 | } 508 | .node-container{ 509 | display: flex; 510 | flex-wrap: wrap; 511 | 512 | /* gap: 20px; */ 513 | /* margin: 20px 0; */ 514 | /* margin-bottom: 100px ; */ 515 | margin-top: 50px ; 516 | 517 | } 518 | 519 | #cluster-area{ 520 | width: 100%; 521 | margin-top:50px; 522 | display:flex; 523 | justify-content: center; 524 | 525 | } 526 | .cluster-circle, .node-circle, .pod-circle{ 527 | background-color: #e8f5ee; 528 | margin: 0 auto; 529 | border: 3px solid #31a870; 530 | box-shadow: 0 4px 6px 0 hsla(0,0%,0%,0.2); 531 | color: hsl(0, 0%, 25%) ; 532 | font-size: large; 533 | 534 | } 535 | .cluster-circle:hover, .node-circle:hover .pod-circle:hover{ 536 | background-color: #31a870; 537 | } 538 | .cluster-circle{ 539 | width: 300px; 540 | height: 300px; 541 | border-radius: 150px; 542 | } 543 | .node-circle{ 544 | width: 200px; 545 | height: 200px; 546 | border-radius:100px; 547 | margin-bottom: 20px;; 548 | } 549 | .pod-circle{ 550 | width: 120px; 551 | height: 120px; 552 | border-radius:60px; 553 | margin-bottom: 20px;; 554 | } 555 | #graph-area{ 556 | width: 100%; 557 | /* background-color: hsl(0, 0%, 100%); */ 558 | display:flex; 559 | margin-top: 20px; 560 | justify-content: space-around; 561 | border-radius: 10px; 562 | background-color: hsl(0, 0%, 100%); 563 | box-shadow: 0 2px 6px 0 hsla(0,0%,0%,0.2); 564 | padding: 30px; 565 | } 566 | #graphs{ 567 | display:flex; 568 | flex-direction:column; 569 | width:100%; 570 | 571 | } 572 | canvas {width:500px; } 573 | 574 | 575 | 576 | 577 | 578 | 579 | /* AddCluster Modal Styles */ 580 | .modal { 581 | display: flex; 582 | position: fixed; 583 | z-index: 1000; 584 | left: 0; 585 | top: 0; 586 | width: 100%; 587 | height: 100%; 588 | overflow: auto; 589 | background-color: rgba(0, 0, 0, 0.4); 590 | justify-content: center; 591 | align-items: center; 592 | } 593 | 594 | .modalContent { 595 | background-color: #fff; 596 | margin: auto; 597 | padding: 20px; 598 | border: 1px solid #888; 599 | width: 80%; 600 | max-width: 500px; 601 | border-radius: 10px; 602 | } 603 | 604 | .modalContent h2 { 605 | color: #333; 606 | font-size: 24px; 607 | margin-bottom: 10px; 608 | } 609 | 610 | .modalContent p { 611 | color: #666; 612 | font-size: 16px; 613 | line-height: 1.5; 614 | margin-bottom: 10px; 615 | 616 | } 617 | .closeButton { 618 | color: #aaa; 619 | float: right; 620 | font-size: 28px; 621 | font-weight: bold; 622 | } 623 | 624 | .closeButton:hover, 625 | .closeButton:focus { 626 | color: #000; 627 | text-decoration: none; 628 | cursor: pointer; 629 | } 630 | 631 | 632 | 633 | /* Read More Button Styles */ 634 | .readMore button { 635 | /* margin-bottom: 10px; 636 | background-color: #5d6a7c; 637 | color: white; 638 | border: none; 639 | padding: 10px 20px; 640 | cursor: pointer; 641 | border-radius: 5px; 642 | width: 150px; */ 643 | 644 | margin-bottom: 15px; 645 | text-decoration: none; 646 | color: #343940; /* Adjust color as needed */ 647 | font-size: small; 648 | border: 1px solid #5d6a7c; 649 | padding: 5px; 650 | cursor: pointer; 651 | border-radius: 5px; 652 | } 653 | 654 | .readMore button:hover { 655 | color: #1F87CA; 656 | border: 1px solid #19648D; 657 | } 658 | -------------------------------------------------------------------------------- /client/components/dashboard/widgets/SampleData.js: -------------------------------------------------------------------------------- 1 | //Use temporary data for now, should ultimately be fecthed data 2 | 3 | // //Sample data 1-->12 Entries over 1 hour for one query 4 | // const results = [ 5 | // { 6 | // Timestamps: [ 7 | // '2024-05-20T17:09:00.000Z', 8 | // '2024-05-20T17:04:00.000Z', 9 | // '2024-05-20T16:59:00.000Z', 10 | // '2024-05-20T16:54:00.000Z', 11 | // '2024-05-20T16:49:00.000Z', 12 | // '2024-05-20T16:44:00.000Z', 13 | // '2024-05-20T16:39:00.000Z', 14 | // '2024-05-20T16:34:00.000Z', 15 | // '2024-05-20T16:29:00.000Z', 16 | // '2024-05-20T16:24:00.000Z', 17 | // '2024-05-20T16:19:00.000Z', 18 | // '2024-05-20T16:14:00.000Z' 19 | // ], 20 | // Values:[ 21 | // 0.07473635170918193, 22 | // 0.07729661186572193, 23 | // 0.0750469506914183, 24 | // 0.07801263615465083, 25 | // 0.07487194454494137, 26 | // 0.07400778949384972, 27 | // 0.07418980145037643, 28 | // 0.07645512991907011, 29 | // 0.07745752357618287, 30 | // 0.07617009018990356, 31 | // 0.07543329715215659, 32 | // 0.0749227148829265 33 | // ] 34 | // } 35 | // ] 36 | 37 | // //Sample data 2-->60 Entries over 1 hour for 2 different metric queries 38 | 39 | 40 | // export const results2 = [ 41 | // { 42 | // "Id": "m1", 43 | // "Label": "container_cpu_utilization", 44 | // "Timestamps": [ 45 | // "2024-05-20T23:27:00.000Z", 46 | // "2024-05-20T23:26:00.000Z", 47 | // "2024-05-20T23:25:00.000Z", 48 | // "2024-05-20T23:24:00.000Z", 49 | // "2024-05-20T23:23:00.000Z", 50 | // "2024-05-20T23:22:00.000Z", 51 | // "2024-05-20T23:21:00.000Z", 52 | // "2024-05-20T23:20:00.000Z", 53 | // "2024-05-20T23:19:00.000Z", 54 | // "2024-05-20T23:18:00.000Z", 55 | // "2024-05-20T23:17:00.000Z", 56 | // "2024-05-20T23:16:00.000Z", 57 | // "2024-05-20T23:15:00.000Z", 58 | // "2024-05-20T23:14:00.000Z", 59 | // "2024-05-20T23:13:00.000Z", 60 | // "2024-05-20T23:12:00.000Z", 61 | // "2024-05-20T23:11:00.000Z", 62 | // "2024-05-20T23:10:00.000Z", 63 | // "2024-05-20T23:09:00.000Z", 64 | // "2024-05-20T23:08:00.000Z", 65 | // "2024-05-20T23:07:00.000Z", 66 | // "2024-05-20T23:06:00.000Z", 67 | // "2024-05-20T23:05:00.000Z", 68 | // "2024-05-20T23:04:00.000Z", 69 | // "2024-05-20T23:03:00.000Z", 70 | // "2024-05-20T23:02:00.000Z", 71 | // "2024-05-20T23:01:00.000Z", 72 | // "2024-05-20T23:00:00.000Z", 73 | // "2024-05-20T22:59:00.000Z", 74 | // "2024-05-20T22:58:00.000Z", 75 | // "2024-05-20T22:57:00.000Z", 76 | // "2024-05-20T22:56:00.000Z", 77 | // "2024-05-20T22:55:00.000Z", 78 | // "2024-05-20T22:54:00.000Z", 79 | // "2024-05-20T22:53:00.000Z", 80 | // "2024-05-20T22:52:00.000Z", 81 | // "2024-05-20T22:51:00.000Z", 82 | // "2024-05-20T22:50:00.000Z", 83 | // "2024-05-20T22:49:00.000Z", 84 | // "2024-05-20T22:48:00.000Z", 85 | // "2024-05-20T22:47:00.000Z", 86 | // "2024-05-20T22:46:00.000Z", 87 | // "2024-05-20T22:45:00.000Z", 88 | // "2024-05-20T22:44:00.000Z", 89 | // "2024-05-20T22:43:00.000Z", 90 | // "2024-05-20T22:42:00.000Z", 91 | // "2024-05-20T22:41:00.000Z", 92 | // "2024-05-20T22:40:00.000Z", 93 | // "2024-05-20T22:39:00.000Z", 94 | // "2024-05-20T22:38:00.000Z", 95 | // "2024-05-20T22:37:00.000Z", 96 | // "2024-05-20T22:36:00.000Z", 97 | // "2024-05-20T22:35:00.000Z", 98 | // "2024-05-20T22:34:00.000Z", 99 | // "2024-05-20T22:33:00.000Z", 100 | // "2024-05-20T22:32:00.000Z", 101 | // "2024-05-20T22:31:00.000Z", 102 | // "2024-05-20T22:30:00.000Z", 103 | // "2024-05-20T22:29:00.000Z", 104 | // "2024-05-20T22:28:00.000Z" 105 | // ], 106 | // "Values": [ 107 | // 0.06481481869581435, 108 | // 0.0678928446624351, 109 | // 0.0689998936131094, 110 | // 0.06301990932208944, 111 | // 0.06627987243695134, 112 | // 0.06493561630229597, 113 | // 0.06536219884301392, 114 | // 0.06573918155288322, 115 | // 0.06422984714006222, 116 | // 0.06561958961738129, 117 | // 0.06938708497932466, 118 | // 0.06361462182441847, 119 | // 0.06485725939241656, 120 | // 0.0642573168984206, 121 | // 0.07047077217557414, 122 | // 0.06823247194804394, 123 | // 0.06869659840054759, 124 | // 0.06089940468361541, 125 | // 0.06532404225405519, 126 | // 0.06547285646953072, 127 | // 0.06549708360911798, 128 | // 0.06545207512393215, 129 | // 0.06710712395050339, 130 | // 0.06147331926092301, 131 | // 0.06747391355276847, 132 | // 0.06407483999822745, 133 | // 0.06911082557388103, 134 | // 0.06395317994826752, 135 | // 0.06563108150700182, 136 | // 0.0636050486424766, 137 | // 0.06857062646940149, 138 | // 0.06910785165894744, 139 | // 0.06599870983127687, 140 | // 0.06665941861236513, 141 | // 0.06563699036021586, 142 | // 0.06511372164972626, 143 | // 0.0643056192781604, 144 | // 0.06933491812287719, 145 | // 0.0654939971952009, 146 | // 0.06470594444988563, 147 | // 0.06828132364919093, 148 | // 0.06420866936497502, 149 | // 0.06582174534721519, 150 | // 0.06460367851657171, 151 | // 0.06617420019119337, 152 | // 0.06696207921786058, 153 | // 0.06793073596648178, 154 | // 0.06173281159821925, 155 | // 0.06998416938861704, 156 | // 0.06246160332412403, 157 | // 0.06614100853026883, 158 | // 0.06550001446322107, 159 | // 0.0636650838852299, 160 | // 0.06495610633881825, 161 | // 0.06591784238072265, 162 | // 0.06518409597511728, 163 | // 0.06525568645055443, 164 | // 0.06371444670733875, 165 | // 0.07286858097793336, 166 | // 0.06411757322527087 167 | // ], 168 | // "StatusCode": "Complete", 169 | // "Messages": [] 170 | // }, 171 | // { 172 | // "Id": "m2", 173 | // "Label": "container_memory_utilization", 174 | // "Timestamps": [ 175 | // "2024-05-20T23:27:00.000Z", 176 | // "2024-05-20T23:26:00.000Z", 177 | // "2024-05-20T23:25:00.000Z", 178 | // "2024-05-20T23:24:00.000Z", 179 | // "2024-05-20T23:23:00.000Z", 180 | // "2024-05-20T23:22:00.000Z", 181 | // "2024-05-20T23:21:00.000Z", 182 | // "2024-05-20T23:20:00.000Z", 183 | // "2024-05-20T23:19:00.000Z", 184 | // "2024-05-20T23:18:00.000Z", 185 | // "2024-05-20T23:17:00.000Z", 186 | // "2024-05-20T23:16:00.000Z", 187 | // "2024-05-20T23:15:00.000Z", 188 | // "2024-05-20T23:14:00.000Z", 189 | // "2024-05-20T23:13:00.000Z", 190 | // "2024-05-20T23:12:00.000Z", 191 | // "2024-05-20T23:11:00.000Z", 192 | // "2024-05-20T23:10:00.000Z", 193 | // "2024-05-20T23:09:00.000Z", 194 | // "2024-05-20T23:08:00.000Z", 195 | // "2024-05-20T23:07:00.000Z", 196 | // "2024-05-20T23:06:00.000Z", 197 | // "2024-05-20T23:05:00.000Z", 198 | // "2024-05-20T23:04:00.000Z", 199 | // "2024-05-20T23:03:00.000Z", 200 | // "2024-05-20T23:02:00.000Z", 201 | // "2024-05-20T23:01:00.000Z", 202 | // "2024-05-20T23:00:00.000Z", 203 | // "2024-05-20T22:59:00.000Z", 204 | // "2024-05-20T22:58:00.000Z", 205 | // "2024-05-20T22:57:00.000Z", 206 | // "2024-05-20T22:56:00.000Z", 207 | // "2024-05-20T22:55:00.000Z", 208 | // "2024-05-20T22:54:00.000Z", 209 | // "2024-05-20T22:53:00.000Z", 210 | // "2024-05-20T22:52:00.000Z", 211 | // "2024-05-20T22:51:00.000Z", 212 | // "2024-05-20T22:50:00.000Z", 213 | // "2024-05-20T22:49:00.000Z", 214 | // "2024-05-20T22:48:00.000Z", 215 | // "2024-05-20T22:47:00.000Z", 216 | // "2024-05-20T22:46:00.000Z", 217 | // "2024-05-20T22:45:00.000Z", 218 | // "2024-05-20T22:44:00.000Z", 219 | // "2024-05-20T22:43:00.000Z", 220 | // "2024-05-20T22:42:00.000Z", 221 | // "2024-05-20T22:41:00.000Z", 222 | // "2024-05-20T22:40:00.000Z", 223 | // "2024-05-20T22:39:00.000Z", 224 | // "2024-05-20T22:38:00.000Z", 225 | // "2024-05-20T22:37:00.000Z", 226 | // "2024-05-20T22:36:00.000Z", 227 | // "2024-05-20T22:35:00.000Z", 228 | // "2024-05-20T22:34:00.000Z", 229 | // "2024-05-20T22:33:00.000Z", 230 | // "2024-05-20T22:32:00.000Z", 231 | // "2024-05-20T22:31:00.000Z", 232 | // "2024-05-20T22:30:00.000Z", 233 | // "2024-05-20T22:29:00.000Z", 234 | // "2024-05-20T22:28:00.000Z" 235 | // ], 236 | // "Values": [ 237 | // 0.31019005367370134, 238 | // 0.30735913529054804, 239 | // 0.3144524070858079, 240 | // 0.3168647585296913, 241 | // 0.31320948693789974, 242 | // 0.30741664830510423, 243 | // 0.3107108659721821, 244 | // 0.30920274692382055, 245 | // 0.3144268457460051, 246 | // 0.311279605782793, 247 | // 0.3108706243459492, 248 | // 0.31434377139164627, 249 | // 0.31408176765866824, 250 | // 0.31321907244032576, 251 | // 0.31138824147695465, 252 | // 0.31082269683381913, 253 | // 0.31467606880908183, 254 | // 0.31456423794744487, 255 | // 0.314845412685275, 256 | // 0.3137334944038559, 257 | // 0.3113722656395779, 258 | // 0.3142031840227312, 259 | // 0.3135961022024162, 260 | // 0.3153023216342489, 261 | // 0.31344592933107507, 262 | // 0.30644212222512485, 263 | // 0.3093081874505069, 264 | // 0.3084103453899357, 265 | // 0.31347149067087776, 266 | // 0.3090461837175288, 267 | // 0.305790308060155, 268 | // 0.3142287453625339, 269 | // 0.31298902038210114, 270 | // 0.31473997215858873, 271 | // 0.3133404888043888, 272 | // 0.3136983475616271, 273 | // 0.30710671705999604, 274 | // 0.3131455835883929, 275 | // 0.3125353066006025, 276 | // 0.31105913922699446, 277 | // 0.31028910386543684, 278 | // 0.30682554232216597, 279 | // 0.31154160951577115, 280 | // 0.30733037878326996, 281 | // 0.31299860588452716, 282 | // 0.3158774517798105, 283 | // 0.3136791765567751, 284 | // 0.3132765854548819, 285 | // 0.31421915986010784, 286 | // 0.30982260941403694, 287 | // 0.30764989553080424, 288 | // 0.31373029923638057, 289 | // 0.3123947192316875, 290 | // 0.31299541071705184, 291 | // 0.3121550816710368, 292 | // 0.3138868624426723, 293 | // 0.312343596552082, 294 | // 0.3065890999289906, 295 | // 0.3135929070349408, 296 | // 0.31140421731433143 297 | // ], 298 | // "StatusCode": "Complete", 299 | // "Messages": [] 300 | // } 301 | // ] 302 | 303 | //sample data 3 20 entries across days 304 | export const results = [ 305 | { 306 | "Id": "m1", 307 | "Label": "container_cpu_utilization", 308 | "Timestamps": [ 309 | "2024-05-22T23:12:00.000Z", "2024-05-22T21:12:00.000Z", "2024-05-22T19:12:00.000Z", 310 | "2024-05-22T17:12:00.000Z", "2024-05-22T15:12:00.000Z", "2024-05-22T13:12:00.000Z", 311 | "2024-05-22T11:12:00.000Z", "2024-05-22T09:12:00.000Z", "2024-05-22T07:12:00.000Z", 312 | "2024-05-22T05:12:00.000Z", "2024-05-22T03:12:00.000Z", "2024-05-22T01:12:00.000Z", 313 | "2024-05-21T23:12:00.000Z", "2024-05-21T21:12:00.000Z", "2024-05-21T19:12:00.000Z", 314 | "2024-05-21T17:12:00.000Z", "2024-05-21T15:12:00.000Z", "2024-05-21T13:12:00.000Z", 315 | "2024-05-21T11:12:00.000Z", "2024-05-21T09:12:00.000Z", "2024-05-21T07:12:00.000Z", 316 | "2024-05-21T05:12:00.000Z", "2024-05-21T03:12:00.000Z", "2024-05-21T01:12:00.000Z", 317 | "2024-05-20T23:12:00.000Z", "2024-05-20T21:12:00.000Z", "2024-05-20T19:12:00.000Z", 318 | "2024-05-20T17:12:00.000Z", "2024-05-20T15:12:00.000Z", "2024-05-20T13:12:00.000Z", 319 | "2024-05-20T11:12:00.000Z", "2024-05-20T09:12:00.000Z", "2024-05-20T07:12:00.000Z", 320 | "2024-05-20T05:12:00.000Z", "2024-05-20T03:12:00.000Z", "2024-05-20T01:12:00.000Z", 321 | "2024-05-19T23:12:00.000Z", "2024-05-19T21:12:00.000Z", "2024-05-19T19:12:00.000Z", 322 | "2024-05-19T17:12:00.000Z", "2024-05-19T15:12:00.000Z", "2024-05-19T13:12:00.000Z", 323 | "2024-05-19T11:12:00.000Z", "2024-05-19T09:12:00.000Z", "2024-05-19T07:12:00.000Z", 324 | "2024-05-19T05:12:00.000Z", "2024-05-19T03:12:00.000Z", "2024-05-19T01:12:00.000Z", 325 | "2024-05-18T23:12:00.000Z", "2024-05-18T21:12:00.000Z", "2024-05-18T19:12:00.000Z", 326 | "2024-05-18T17:12:00.000Z", "2024-05-18T15:12:00.000Z", "2024-05-18T13:12:00.000Z", 327 | "2024-05-18T11:12:00.000Z", "2024-05-18T09:12:00.000Z", "2024-05-18T07:12:00.000Z", 328 | "2024-05-18T05:12:00.000Z", "2024-05-18T03:12:00.000Z", "2024-05-18T01:12:00.000Z", 329 | "2024-05-17T23:12:00.000Z", "2024-05-17T21:12:00.000Z", "2024-05-17T19:12:00.000Z", 330 | "2024-05-17T17:12:00.000Z", "2024-05-17T15:12:00.000Z", "2024-05-17T13:12:00.000Z", 331 | "2024-05-17T11:12:00.000Z", "2024-05-17T09:12:00.000Z", "2024-05-17T07:12:00.000Z", 332 | "2024-05-17T05:12:00.000Z", "2024-05-17T03:12:00.000Z", "2024-05-17T01:12:00.000Z", 333 | "2024-05-16T23:12:00.000Z", "2024-05-16T21:12:00.000Z", "2024-05-16T19:12:00.000Z", 334 | "2024-05-16T17:12:00.000Z", "2024-05-16T15:12:00.000Z" 335 | ], 336 | "Values": [ 337 | 0.07597333922102548, 0.07611605763101228, 0.07602207660079069, 338 | 0.0763541181890077, 0.07666897373988303, 0.07617588332431642, 339 | 0.07583242617439744, 0.07614093478129808, 0.07606720559960778, 340 | 0.07599497133385971, 0.07624153931256002, 0.0762484728519719, 341 | 0.07628226965682178, 0.07663292330315705, 0.07612429831802885, 342 | 0.07627228850952728, 0.07595793786558606, 0.07546075111940723, 343 | 0.07574598055513757, 0.07563834390463152, 0.07574498590162296, 344 | 0.07620310863654509, 0.07590733153349276, 0.07555960814605925, 345 | 0.07559922315315859, 0.07559666069229447, 0.07541409962394025, 346 | 0.07569773274942182, 0.07567222686472141, 0.07582916922004558, 347 | 0.07610236546915877, 0.07562892337652914, 0.07540388161817325, 348 | 0.07576317057568081, 0.07559017017242027, 0.07551101932661677, 349 | 0.0756032061787719, 0.07582687277928826, 0.07529024274235985, 350 | 0.0752662578977809, 0.0752779994621395, 0.07541138720214255, 351 | 0.0752384424271686, 0.07517996504034127, 0.07544122107099552, 352 | 0.07516697521259703, 0.07506443304134683, 0.07543842473708584, 353 | 0.07534655002629792, 0.07581577968142648, 0.07541583919511556, 354 | 0.07529586058507706, 0.07577696876794789, 0.07538528543581982, 355 | 0.07524097709880799, 0.07486870130700798, 0.07533631627005424, 356 | 0.07514249257173312, 0.07515158595457161, 0.07535522499458673, 357 | 0.07535122370310754, 0.07541157620951618, 0.07545156022763458, 358 | 0.07511769863440862, 0.07562628179122347, 0.0752829983023772, 359 | 0.07608389320352411, 0.07635623555103019, 0.07583694434059705, 360 | 0.07581930281943555, 0.0753785080286412, 0.07524583169106948, 361 | 0.07508632653394035, 0.07512529413903463, 0.07511439510347798, 362 | 0.07518561681133623, 0.07510806839031087 363 | ], 364 | "StatusCode": "Complete", 365 | "Messages": [] 366 | }, 367 | { 368 | "Id": "m2", 369 | "Label": "container_memory_utilization", 370 | "Timestamps": [ 371 | "2024-05-22T23:12:00.000Z", "2024-05-22T21:12:00.000Z", "2024-05-22T19:12:00.000Z", 372 | "2024-05-22T17:12:00.000Z", "2024-05-22T15:12:00.000Z", "2024-05-22T13:12:00.000Z", 373 | "2024-05-22T11:12:00.000Z", "2024-05-22T09:12:00.000Z", "2024-05-22T07:12:00.000Z", 374 | "2024-05-22T05:12:00.000Z", "2024-05-22T03:12:00.000Z", "2024-05-22T01:12:00.000Z", 375 | "2024-05-21T23:12:00.000Z", "2024-05-21T21:12:00.000Z", "2024-05-21T19:12:00.000Z", 376 | "2024-05-21T17:12:00.000Z", "2024-05-21T15:12:00.000Z", "2024-05-21T13:12:00.000Z", 377 | "2024-05-21T11:12:00.000Z", "2024-05-21T09:12:00.000Z", "2024-05-21T07:12:00.000Z", 378 | "2024-05-21T05:12:00.000Z", "2024-05-21T03:12:00.000Z", "2024-05-21T01:12:00.000Z", 379 | "2024-05-20T23:12:00.000Z", "2024-05-20T21:12:00.000Z", "2024-05-20T19:12:00.000Z", 380 | "2024-05-20T17:12:00.000Z", "2024-05-20T15:12:00.000Z", "2024-05-20T13:12:00.000Z", 381 | "2024-05-20T11:12:00.000Z", "2024-05-20T09:12:00.000Z", "2024-05-20T07:12:00.000Z", 382 | "2024-05-20T05:12:00.000Z", "2024-05-20T03:12:00.000Z", "2024-05-20T01:12:00.000Z", 383 | "2024-05-19T23:12:00.000Z", "2024-05-19T21:12:00.000Z", "2024-05-19T19:12:00.000Z", 384 | "2024-05-19T17:12:00.000Z", "2024-05-19T15:12:00.000Z", "2024-05-19T13:12:00.000Z", 385 | "2024-05-19T11:12:00.000Z", "2024-05-19T09:12:00.000Z", "2024-05-19T07:12:00.000Z", 386 | "2024-05-19T05:12:00.000Z", "2024-05-19T03:12:00.000Z", "2024-05-19T01:12:00.000Z", 387 | "2024-05-18T23:12:00.000Z", "2024-05-18T21:12:00.000Z", "2024-05-18T19:12:00.000Z", 388 | "2024-05-18T17:12:00.000Z", "2024-05-18T15:12:00.000Z", "2024-05-18T13:12:00.000Z", 389 | "2024-05-18T11:12:00.000Z", "2024-05-18T09:12:00.000Z", "2024-05-18T07:12:00.000Z", 390 | "2024-05-18T05:12:00.000Z", "2024-05-18T03:12:00.000Z", "2024-05-18T01:12:00.000Z", 391 | "2024-05-17T23:12:00.000Z", "2024-05-17T21:12:00.000Z", "2024-05-17T19:12:00.000Z", 392 | "2024-05-17T17:12:00.000Z", "2024-05-17T15:12:00.000Z", "2024-05-17T13:12:00.000Z", 393 | "2024-05-17T11:12:00.000Z", "2024-05-17T09:12:00.000Z", "2024-05-17T07:12:00.000Z", 394 | "2024-05-17T05:12:00.000Z", "2024-05-17T03:12:00.000Z", "2024-05-17T01:12:00.000Z", 395 | "2024-05-16T23:12:00.000Z", "2024-05-16T21:12:00.000Z", "2024-05-16T19:12:00.000Z", 396 | "2024-05-16T17:12:00.000Z", "2024-05-16T15:12:00.000Z" 397 | ], 398 | "Values": [ 399 | 0.37, 0.369254854567053, 0.3688702172633548, 400 | 0.3694353358841604, 0.36912923884002247, 0.36808511896941376, 401 | 0.36883628762778314, 0.3678252453480857, 0.36872771279395433, 402 | 0.36858913381602376, 0.368840639141583, 0.3687966675510891, 403 | 0.3678120995161873, 0.36906040580355387, 0.3688437125883925, 404 | 0.3683994017283215, 0.36783428310751615, 0.3670129424853543, 405 | 0.36756951022939255, 0.36823194452244706, 0.3667948799127038, 406 | 0.3675547820288395, 0.3683477921660533, 0.3668614915470232, 407 | 0.36743053565929834, 0.3676156727918699, 0.3677767700929598, 408 | 0.3674009271073602, 0.36694760891802514, 0.3668215367384983, 409 | 0.36730321584294773, 0.36610734073234297, 0.3667516386462042, 410 | 0.3665479999724425, 0.366506675806428, 0.3670914218845822, 411 | 0.36687223339577363, 0.36586326036739336, 0.3664313002841765, 412 | 0.36652304723596846, 0.3663138094115832, 0.366046449969313, 413 | 0.3656583436266414, 0.3659943839545483, 0.3655852199367058, 414 | 0.36588313126607347, 0.3657240423559677, 0.36539883516731064, 415 | 0.36520599920263225, 0.3645415869487597, 0.36457335604251434, 416 | 0.36472243342786387, 0.36415917104721085, 0.36474385626503203, 417 | 0.3642825349419253, 0.36479817411211285, 0.36464620586095237, 418 | 0.3633563015360734, 0.36414152155068014, 0.36338706643433605, 419 | 0.3630677626979668, 0.3630844384291714, 0.36345824259362003, 420 | 0.36254740685198267, 0.36291804627912216, 0.36303520241988463, 421 | 0.3622227169761549, 0.3618790082463072, 0.3618882285867361, 422 | 0.367, 0.36204975191015626, 0.3620025547220206, 423 | 0.360841004839151, 0.3602971264745152, 0.35972205718928607, 424 | 0.358769319108472, 0.35755512503767567 425 | ], 426 | "StatusCode": "Complete", 427 | "Messages": [] 428 | } 429 | ] 430 | 431 | // //sample data 4 20 entries across days, utilization manipulated to look good 432 | // export const results = [ 433 | // { 434 | // "Id": "m1", 435 | // "Label": "container_cpu_utilization", 436 | // "Timestamps": [ 437 | // "2024-05-22T23:12:00.000Z", "2024-05-22T21:12:00.000Z", "2024-05-22T19:12:00.000Z", 438 | // "2024-05-22T17:12:00.000Z", "2024-05-22T15:12:00.000Z", "2024-05-22T13:12:00.000Z", 439 | // "2024-05-22T11:12:00.000Z", "2024-05-22T09:12:00.000Z", "2024-05-22T07:12:00.000Z", 440 | // "2024-05-22T05:12:00.000Z", "2024-05-22T03:12:00.000Z", "2024-05-22T01:12:00.000Z", 441 | // "2024-05-21T23:12:00.000Z", "2024-05-21T21:12:00.000Z", "2024-05-21T19:12:00.000Z", 442 | // "2024-05-21T17:12:00.000Z", "2024-05-21T15:12:00.000Z", "2024-05-21T13:12:00.000Z", 443 | // "2024-05-21T11:12:00.000Z", "2024-05-21T09:12:00.000Z", "2024-05-21T07:12:00.000Z", 444 | // "2024-05-21T05:12:00.000Z", "2024-05-21T03:12:00.000Z", "2024-05-21T01:12:00.000Z", 445 | // "2024-05-20T23:12:00.000Z", "2024-05-20T21:12:00.000Z", "2024-05-20T19:12:00.000Z", 446 | // "2024-05-20T17:12:00.000Z", "2024-05-20T15:12:00.000Z", "2024-05-20T13:12:00.000Z", 447 | // "2024-05-20T11:12:00.000Z", "2024-05-20T09:12:00.000Z", "2024-05-20T07:12:00.000Z", 448 | // "2024-05-20T05:12:00.000Z", "2024-05-20T03:12:00.000Z", "2024-05-20T01:12:00.000Z", 449 | // "2024-05-19T23:12:00.000Z", "2024-05-19T21:12:00.000Z", "2024-05-19T19:12:00.000Z", 450 | // "2024-05-19T17:12:00.000Z", "2024-05-19T15:12:00.000Z", "2024-05-19T13:12:00.000Z", 451 | // "2024-05-19T11:12:00.000Z", "2024-05-19T09:12:00.000Z", "2024-05-19T07:12:00.000Z", 452 | // "2024-05-19T05:12:00.000Z", "2024-05-19T03:12:00.000Z", "2024-05-19T01:12:00.000Z", 453 | // "2024-05-18T23:12:00.000Z", "2024-05-18T21:12:00.000Z", "2024-05-18T19:12:00.000Z", 454 | // "2024-05-18T17:12:00.000Z", "2024-05-18T15:12:00.000Z", "2024-05-18T13:12:00.000Z", 455 | // "2024-05-18T11:12:00.000Z", "2024-05-18T09:12:00.000Z", "2024-05-18T07:12:00.000Z", 456 | // "2024-05-18T05:12:00.000Z", "2024-05-18T03:12:00.000Z", "2024-05-18T01:12:00.000Z", 457 | // "2024-05-17T23:12:00.000Z", "2024-05-17T21:12:00.000Z", "2024-05-17T19:12:00.000Z", 458 | // "2024-05-17T17:12:00.000Z", "2024-05-17T15:12:00.000Z", "2024-05-17T13:12:00.000Z", 459 | // "2024-05-17T11:12:00.000Z", "2024-05-17T09:12:00.000Z", "2024-05-17T07:12:00.000Z", 460 | // "2024-05-17T05:12:00.000Z", "2024-05-17T03:12:00.000Z", "2024-05-17T01:12:00.000Z", 461 | // "2024-05-16T23:12:00.000Z", "2024-05-16T21:12:00.000Z", "2024-05-16T19:12:00.000Z", 462 | // "2024-05-16T17:12:00.000Z", "2024-05-16T15:12:00.000Z" 463 | // ], 464 | // "Values": [ 465 | // 0.07597333922102548, 0.07611605763101228, 0.07602207660079069, 466 | // 0.0763541181890077, 0.07666897373988303, 0.07617588332431642, 467 | // 0.07583242617439744, 0.07614093478129808, 0.07606720559960778, 468 | // 0.07599497133385971, 0.07624153931256002, 0.0762484728519719, 469 | // 0.07628226965682178, 0.07663292330315705, 0.07612429831802885, 470 | // 0.07627228850952728, 0.07595793786558606, 0.07546075111940723, 471 | // 0.07574598055513757, 0.07563834390463152, 0.07574498590162296, 472 | // 0.07620310863654509, 0.07590733153349276, 0.07555960814605925, 473 | // 0.07559922315315859, 0.07559666069229447, 0.07541409962394025, 474 | // 0.07569773274942182, 0.07567222686472141, 0.07582916922004558, 475 | // 0.07610236546915877, 0.07562892337652914, 0.07540388161817325, 476 | // 0.07576317057568081, 0.07559017017242027, 0.07551101932661677, 477 | // 0.0756032061787719, 0.07582687277928826, 0.07529024274235985, 478 | // 0.0752662578977809, 0.0752779994621395, 0.07541138720214255, 479 | // 0.0752384424271686, 0.07517996504034127, 0.07544122107099552, 480 | // 0.07516697521259703, 0.07506443304134683, 0.07543842473708584, 481 | // 0.07534655002629792, 0.07581577968142648, 0.07541583919511556, 482 | // 0.07529586058507706, 0.07577696876794789, 0.07538528543581982, 483 | // 0.07524097709880799, 0.07486870130700798, 0.07533631627005424, 484 | // 0.07514249257173312, 0.07515158595457161, 0.07535522499458673, 485 | // 0.07535122370310754, 0.07541157620951618, 0.07545156022763458, 486 | // 0.07511769863440862, 0.07562628179122347, 0.0752829983023772, 487 | // 0.07608389320352411, 0.07635623555103019, 0.07583694434059705, 488 | // 0.07581930281943555, 0.0753785080286412, 0.07524583169106948, 489 | // 0.07508632653394035, 0.07512529413903463, 0.07511439510347798, 490 | // 0.07518561681133623, 0.07510806839031087 491 | // ], 492 | // "StatusCode": "Complete", 493 | // "Messages": [] 494 | // }, 495 | // { 496 | // "Id": "m2", 497 | // "Label": "container_memory_utilization", 498 | // "Timestamps": [ 499 | // "2024-05-22T23:12:00.000Z", "2024-05-22T21:12:00.000Z", "2024-05-22T19:12:00.000Z", 500 | // "2024-05-22T17:12:00.000Z", "2024-05-22T15:12:00.000Z", "2024-05-22T13:12:00.000Z", 501 | // "2024-05-22T11:12:00.000Z", "2024-05-22T09:12:00.000Z", "2024-05-22T07:12:00.000Z", 502 | // "2024-05-22T05:12:00.000Z", "2024-05-22T03:12:00.000Z", "2024-05-22T01:12:00.000Z", 503 | // "2024-05-21T23:12:00.000Z", "2024-05-21T21:12:00.000Z", "2024-05-21T19:12:00.000Z", 504 | // "2024-05-21T17:12:00.000Z", "2024-05-21T15:12:00.000Z", "2024-05-21T13:12:00.000Z", 505 | // "2024-05-21T11:12:00.000Z", "2024-05-21T09:12:00.000Z", "2024-05-21T07:12:00.000Z", 506 | // "2024-05-21T05:12:00.000Z", "2024-05-21T03:12:00.000Z", "2024-05-21T01:12:00.000Z", 507 | // "2024-05-20T23:12:00.000Z", "2024-05-20T21:12:00.000Z", "2024-05-20T19:12:00.000Z", 508 | // "2024-05-20T17:12:00.000Z", "2024-05-20T15:12:00.000Z", "2024-05-20T13:12:00.000Z", 509 | // "2024-05-20T11:12:00.000Z", "2024-05-20T09:12:00.000Z", "2024-05-20T07:12:00.000Z", 510 | // "2024-05-20T05:12:00.000Z", "2024-05-20T03:12:00.000Z", "2024-05-20T01:12:00.000Z", 511 | // "2024-05-19T23:12:00.000Z", "2024-05-19T21:12:00.000Z", "2024-05-19T19:12:00.000Z", 512 | // "2024-05-19T17:12:00.000Z", "2024-05-19T15:12:00.000Z", "2024-05-19T13:12:00.000Z", 513 | // "2024-05-19T11:12:00.000Z", "2024-05-19T09:12:00.000Z", "2024-05-19T07:12:00.000Z", 514 | // "2024-05-19T05:12:00.000Z", "2024-05-19T03:12:00.000Z", "2024-05-19T01:12:00.000Z", 515 | // "2024-05-18T23:12:00.000Z", "2024-05-18T21:12:00.000Z", "2024-05-18T19:12:00.000Z", 516 | // "2024-05-18T17:12:00.000Z", "2024-05-18T15:12:00.000Z", "2024-05-18T13:12:00.000Z", 517 | // "2024-05-18T11:12:00.000Z", "2024-05-18T09:12:00.000Z", "2024-05-18T07:12:00.000Z", 518 | // "2024-05-18T05:12:00.000Z", "2024-05-18T03:12:00.000Z", "2024-05-18T01:12:00.000Z", 519 | // "2024-05-17T23:12:00.000Z", "2024-05-17T21:12:00.000Z", "2024-05-17T19:12:00.000Z", 520 | // "2024-05-17T17:12:00.000Z", "2024-05-17T15:12:00.000Z", "2024-05-17T13:12:00.000Z", 521 | // "2024-05-17T11:12:00.000Z", "2024-05-17T09:12:00.000Z", "2024-05-17T07:12:00.000Z", 522 | // "2024-05-17T05:12:00.000Z", "2024-05-17T03:12:00.000Z", "2024-05-17T01:12:00.000Z", 523 | // "2024-05-16T23:12:00.000Z", "2024-05-16T21:12:00.000Z", "2024-05-16T19:12:00.000Z", 524 | // "2024-05-16T17:12:00.000Z", "2024-05-16T15:12:00.000Z" 525 | // ], 526 | // "Values": [ 527 | // 0.369248681590434, 0.369254854567053, 0.3688702172633548, 528 | // 0.3694353358841604, 0.36912923884002247, 0.36808511896941376, 529 | // 0.36883628762778314, 0.3678252453480857, 0.36872771279395433, 530 | // 0.36858913381602376, 0.368840639141583, 0.3687966675510891, 531 | // 0.3678120995161873, 0.36906040580355387, 0.3688437125883925, 532 | // 0.3683994017283215, 0.36783428310751615, 0.3670129424853543, 533 | // 0.36756951022939255, 0.36823194452244706, 0.3667948799127038, 534 | // 0.3675547820288395, 0.3683477921660533, 0.3668614915470232, 535 | // 0.36743053565929834, 0.3676156727918699, 0.3677767700929598, 536 | // 0.3674009271073602, 0.36694760891802514, 0.3668215367384983, 537 | // 0.36730321584294773, 0.36610734073234297, 0.3667516386462042, 538 | // 0.3665479999724425, 0.366506675806428, 0.3670914218845822, 539 | // 0.36687223339577363, 0.36586326036739336, 0.3664313002841765, 540 | // 0.36652304723596846, 0.3663138094115832, 0.366046449969313, 541 | // 0.3656583436266414, 0.3659943839545483, 0.3655852199367058, 542 | // 0.36588313126607347, 0.3657240423559677, 0.36539883516731064, 543 | // 0.36520599920263225, 0.3645415869487597, 0.36457335604251434, 544 | // 0.36472243342786387, 0.36415917104721085, 0.36474385626503203, 545 | // 0.3642825349419253, 0.36479817411211285, 0.36464620586095237, 546 | // 0.3633563015360734, 0.36414152155068014, 0.36338706643433605, 547 | // 0.3630677626979668, 0.3630844384291714, 0.36345824259362003, 548 | // 0.36254740685198267, 0.36291804627912216, 0.36303520241988463, 549 | // 0.3622227169761549, 0.3618790082463072, 0.3618882285867361, 550 | // 0.3628434010808648, 0.36204975191015626, 0.3620025547220206, 551 | // 0.360841004839151, 0.3602971264745152, 0.35972205718928607, 552 | // 0.358769319108472, 0.35755512503767567 553 | // ], 554 | // "StatusCode": "Complete", 555 | // "Messages": [] 556 | // } 557 | 558 | 559 | // ] --------------------------------------------------------------------------------