├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── client ├── Dialog │ ├── DeploymentDialog.js │ ├── IngressDialog.js │ ├── NodeDialog.js │ ├── PodsDialog.js │ └── ServiceDialog.js ├── actions │ └── actions.js ├── assets │ ├── kubermetrics.png │ └── logo.png ├── components │ ├── Deployments │ │ ├── Deployment.js │ │ ├── Deployment.style.css │ │ ├── DeploymentList.js │ │ └── DeploymentList.style.css │ ├── GrafanaMonitoring │ │ ├── ClusterDashboard.js │ │ └── PodDashboard.js │ ├── Header │ │ ├── Header.js │ │ └── Header.style.css │ ├── NavRoutes │ │ └── index.js │ ├── Node │ │ ├── CurrentNode.js │ │ └── currentNode.style.css │ ├── Pods │ │ ├── Pod.js │ │ ├── Pod.style.css │ │ ├── PodsList.js │ │ └── PodsList.style.css │ ├── PrometheusMonitoring │ │ └── PrometheusAlerts.js │ ├── Services │ │ ├── Service.js │ │ ├── Service.styles.css │ │ ├── ServicesList.js │ │ └── ServicesList.styles.css │ ├── mui-elements.js │ └── sidebar │ │ ├── Sidebar.css │ │ ├── Sidebar.js │ │ └── SidebarData.js ├── constants │ └── actionTypes.js ├── pages │ ├── alerts │ │ └── index.js │ ├── home │ │ └── index.js │ └── metrics │ │ └── index.js ├── reducers │ ├── deploymentListReducer.js │ ├── index.js │ ├── ingressListReducer.js │ ├── namespaceListReducer.js │ ├── nodesListReducer.js │ ├── podsListReducer.js │ └── servicesListReducer.js ├── src │ ├── App.jsx │ └── index.js ├── store.js └── styles.css ├── index.html ├── manifests ├── clusterDashboard.yaml ├── clusterRole.yaml ├── config-map.yaml ├── dashboards │ └── home.json ├── grafana-datasource-config.yaml ├── grafana-deployment.yaml ├── kubermetrics-depl.yaml ├── prometheus-deployment.yaml ├── prometheus-ingress.yaml ├── prometheus-service.yaml └── service.yaml ├── package-lock.json ├── package.json ├── server ├── controllers │ └── k8Controller.js └── server.js ├── skaffold.yaml └── webpack.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | ENV CI=true 4 | 5 | WORKDIR /app 6 | COPY package.json ./ 7 | 8 | RUN npm install 9 | COPY ./ ./ 10 | 11 | CMD ["npm", "run", "dev"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://cdn-images-1.medium.com/max/4002/1*wu68SZJTT1ONtB6k6qja8Q.jpeg) 2 | # Kubermetrics 3 | 4 | ## **What is Kubermetrics?** 5 | 6 | Kubermetrics is an open-source dev tool that provides Kubernetes cluster monitoring as well as data visualization in a simple and easy to understand user interface. Kubermetrics intergrates both the Prometheus and Grafana Dashboards on one page! Allowing for custominzable dashboards and alerts. 7 | 8 | - Kubermetrics Dockerhub: https://hub.docker.com/r/kubermetrics/kubermetrics 9 | - Kubermetrics Github: https://github.com/oslabs-beta/kubermetrics 10 | 11 | # Home page 12 | 13 | Dashboard displaying all nodes, deployments, pods, services & ingresses by namespace. 14 | 15 | Home Page 16 | 17 | # Metrics page 18 | 19 | Our Metrics Page utilizes full Grafana integration for customizable dashboards. 20 | 21 | Metrics 22 | 23 | # Alerts page 24 | 25 | Our Alerts Page utilizes full Prometheus integration for access to alerts, graphs, prom-queries and more! 26 | 27 | Alerts 28 | 29 | 30 | 31 | 32 | # Kubermetrics Setup 33 | 34 | In this readme we will walk you through the setup process for our app. For this setup you will need the following: 35 | 36 | - Start by cloning our repo down to your local machine. 37 | - This app assumes you have direct access to your K8s Cluster from the local machine in which this app is being installed 38 | - Kubectl - Kubernetes CLI installed 39 | - Ports 3000, 3068, & 9090 open. (This can be changed if necessary) 40 | - For the next steps, please refer to first section if you have prometheus and grafana installed or scroll down for full installation. 41 | 42 | 43 | 44 | # I Already Have Prometheus & Grafana Installed! 45 | 46 | ### Great! Here are a couple of notes on how to deploy our app and how it interacts with Prometheus & Grafana. 47 | - First, to install our app, in your terminal navigate to our "manifests folder" 48 | - From this directory run the following command 49 | - ```kubectl apply -f kubermetrics-depl.yaml``` 50 | 51 | ### You've just installed our app on your cluster! Next you will want to open a port to access Kubermetrics from your local host. 52 | - Next, run the following command to access all your current running pods 53 | - ```kubectl get pods``` 54 | 55 | ![](https://cdn-images-1.medium.com/max/2000/1*aQJSh-RCHfo8DWH-TBfkfQ.png) 56 | 57 | - Find your current running Pod for Kubermetrics and run the following command 58 | - ```kubectl port-forward 3068:3068``` 59 | - This will allow you to access our dashboard at localhost:3068 60 | - Our app looks for grafana at localhost:3000 & promethues at local host 9090 61 | - Please run the following commands to ensure grafana and prometheus are up and running at these ports! 62 | - ```kubectl port-forward --namespace= 3000:3000``` 63 | - ```kubectl port-forward --namespace= 9090:9090``` 64 | - Now Navigate to localhost:3068 & enjoy our dashboard with full promethus and grafana integration! 65 | 66 | 67 | 68 | # Prometheus & Grafana Not Currently Installed 69 | ### Don't have prometheus or grafana installed? Don't worry! The process is fast and easy. 70 | - Open a terminal and navigate to the Kubermetrics root directory. 71 | - Run the following command: 72 | ```kubectl create namespace monitoring``` 73 | - This will create the K8s namespace monitoring in which we install Prometheus and Grafana. 74 | - Next to install Kubermetrics, Prometheus & Grafana into your cluster, run the following command: 75 | - ``` kubectl apply -f manifests``` 76 | ### Great now you have everything you need installed! Now to open up some ports. 77 | - Next Please run the following commands 78 | - ```kubectl get pods``` 79 | 80 | ![](https://cdn-images-1.medium.com/max/2000/1*aQJSh-RCHfo8DWH-TBfkfQ.png) 81 | 82 | - Find your current running Pod for Kubermetrics and run the following command 83 | - ```kubectl port-forward 3068:3068``` 84 | - This will allow you to access our dashboard at localhost:3068 85 | - Our app looks for grafana at localhost:3000 & promethues at local host 9090 86 | - Please run the following commands to ensure grafana and prometheus are up and running at these ports! 87 | - ```kubectl port-forward --namespace=monitoring 3000:3000``` 88 | - ```kubectl port-forward --namespace=monitoring 9090:9090``` 89 | - Now Navigate to localhost:3068 & enjoy our dashboard with full promethus and grafana integration! 90 | 91 | 92 | # Notes 93 | 94 | - Our Included Grafana yaml file includes environment variables that we use in order to load specific settings when using Grafana. Feel free to poke around or change other settings in this file and reapply! 95 | - Current App is in Beta. Many more features are planned to be added! 96 | - Feel free to visit our github page @ https://github.com/oslabs-beta/kubermetrics if you have any issues! 97 | 98 | # About the Team 99 | 100 | ## Ahad Rajput 101 | - LinkedIn: https://www.linkedin.com/in/arajput96/ 102 | - Github: https://github.com/arajput96 103 | 104 | ## Dominic DiSalvo 105 | 106 | - LinkedIn: https://www.linkedin.com/in/dominicdisalvo/ 107 | - Github: https://github.com/dominicd17 108 | 109 | ## Justin Buckner 110 | 111 | - LinkedIn: https://www.linkedin.com/in/jbuild/ 112 | - Github: https://github.com/JWadeOn -------------------------------------------------------------------------------- /client/Dialog/DeploymentDialog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************ 3 | * 4 | * @module DeploymentDialog.js 5 | * @author team Kubermetrics 6 | * @date 7 | * @description Dialog Box for Deployment Elements 8 | * 9 | * ************************************ 10 | */ 11 | import * as React from 'react'; 12 | import Button from '@material-ui/core/Button'; 13 | import { } from '@material-ui/core/styles'; 14 | import Dialog from '@material-ui/core/Dialog'; 15 | import DialogTitle from '@material-ui/core/DialogTitle'; 16 | import DialogContent from '@material-ui/core/DialogContent'; 17 | import DialogActions from '@material-ui/core/DialogActions'; 18 | import Typography from '@material-ui/core/Typography'; 19 | import Table from '@material-ui/core/Table'; 20 | import TableBody from '@material-ui/core/TableBody'; 21 | import TableCell from '@material-ui/core/TableCell'; 22 | import TableContainer from '@material-ui/core/TableContainer'; 23 | import TableHead from '@material-ui/core/TableHead'; 24 | import TableRow from '@material-ui/core/TableRow'; 25 | import Paper from '@material-ui/core/Paper'; 26 | 27 | // CSS styles we can apply to elements 28 | 29 | const customStyle = { 30 | color: 'white', 31 | margin: '5px', 32 | padding: '5px', 33 | } 34 | 35 | const customStyle2 = { 36 | color: 'rgb(0, 255, 0)', 37 | margin: '5px', 38 | padding: '5px', 39 | } 40 | 41 | 42 | export default function DeploymentDialog(props) { 43 | 44 | // Utilize React Hooks to handle Opening & Closing Dialog Boxes 45 | 46 | const [open, setOpen] = React.useState(false); 47 | 48 | const handleClickOpen = () => { 49 | setOpen(true); 50 | }; 51 | const handleClose = () => { 52 | setOpen(false); 53 | }; 54 | 55 | // Deconstruct Deployment from props - This will be passed down from Deployment Element 56 | 57 | const { deployment } = props; 58 | 59 | return ( 60 |
61 | 64 | 70 | 71 |
Deployment Info
72 |
73 | 74 | 75 | 76 | 77 | 78 | Key 79 | Value 80 | 81 | 82 | 83 | 84 | 85 | Deployment Name: 86 | 87 | {deployment.name} 88 | 89 | 90 | 91 | Replicas: 92 | 93 | {deployment.replicas} 94 | 95 | 96 | 97 | UID: 98 | 99 | {deployment.uid} 100 | 101 | 102 | 103 | Created: 104 | 105 | {deployment.created} 106 | 107 | 108 | 109 | Strategy Type: 110 | 111 | {deployment.strategy.type} 112 | 113 | 114 | 115 | DNS Policy: 116 | 117 | {deployment.template.spec.dnsPolicy} 118 | 119 | 120 | 121 | Restart Policy: 122 | 123 | {deployment.template.spec.restartPolicy} 124 | 125 | 126 | 127 | Scheduler Name: 128 | 129 | {deployment.template.spec.schedulerName} 130 | 131 | 132 |
133 |
134 |
135 | 136 | 139 | 140 |
141 |
142 | ); 143 | } -------------------------------------------------------------------------------- /client/Dialog/IngressDialog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************ 3 | * 4 | * @module IngressDialog.js 5 | * @author team Kubermetrics 6 | * @date 7 | * @description Dialog Box for Ingress Element 8 | * 9 | * ************************************ 10 | */ 11 | import * as React from 'react'; 12 | import Button from '@material-ui/core/Button'; 13 | import { } from '@material-ui/core/styles'; 14 | import Dialog from '@material-ui/core/Dialog'; 15 | import DialogTitle from '@material-ui/core/DialogTitle'; 16 | import DialogContent from '@material-ui/core/DialogContent'; 17 | import DialogActions from '@material-ui/core/DialogActions'; 18 | import Table from '@material-ui/core/Table'; 19 | import TableBody from '@material-ui/core/TableBody'; 20 | import TableCell from '@material-ui/core/TableCell'; 21 | import TableContainer from '@material-ui/core/TableContainer'; 22 | import TableHead from '@material-ui/core/TableHead'; 23 | import TableRow from '@material-ui/core/TableRow'; 24 | import Paper from '@material-ui/core/Paper'; 25 | 26 | const customStyle = { 27 | color: 'white', 28 | margin: '5px', 29 | padding: '5px', 30 | } 31 | 32 | const customStyle2 = { 33 | color: '#7135f0', 34 | margin: '5px', 35 | padding: '5px', 36 | } 37 | 38 | export default function IngressDialog(props) { 39 | 40 | // Utilize React Hooks to handle Opening & Closing Dialog Boxes 41 | const [open, setOpen] = React.useState(false); 42 | 43 | const handleClickOpen = () => { 44 | setOpen(true); 45 | }; 46 | const handleClose = () => { 47 | setOpen(false); 48 | }; 49 | 50 | // Deconstruct Ingress from props - Passed down from Header.js 51 | const { ingress } = props; 52 | 53 | // Initialize Array for dynamic structure of component - Each path in Ingress will have its own row. 54 | const pathsArr = []; 55 | 56 | // Conditional Statement to ensure Ingress exists - this will help avoid errors 57 | // If Ingress exist, we iterate over the paths key within ingress. This is an array which will hold each specific path. 58 | 59 | if (ingress){ 60 | ingress.paths.forEach((path, ind) => { 61 | let style = customStyle 62 | pathsArr.push( 63 | <> 64 | 65 | 66 | Path: 67 | 68 | {path.path} 69 | 70 | 71 | 72 | Port: 73 | 74 | {path.servicePort} 75 | 76 | 77 | 78 | Service Name: 79 | 80 | {path.serviceName} 81 | 82 | 83 | 84 | Path Type: 85 | 86 | {path.pathType} 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | ) 95 | }) 96 | } 97 | 98 | // Return Button that will open dialog box 99 | 100 | return ( 101 |
102 | 105 | 111 | 112 |
Ingress Information
113 |
114 | 115 | 116 | 117 | 118 | 119 | Key 120 | Value 121 | 122 | 123 | 124 | {pathsArr} 125 | 126 |
127 |
128 |
129 | 130 | 133 | 134 |
135 |
136 | ); 137 | } -------------------------------------------------------------------------------- /client/Dialog/NodeDialog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************ 3 | * 4 | * @module NodeDialog.js 5 | * @author team Kubermetrics 6 | * @date 7 | * @description Dialog Box for Current Node Element 8 | * 9 | * ************************************ 10 | */ 11 | import * as React from 'react'; 12 | import Button from '@material-ui/core/Button'; 13 | import { } from '@material-ui/core/styles'; 14 | import Dialog from '@material-ui/core/Dialog'; 15 | import DialogTitle from '@material-ui/core/DialogTitle'; 16 | import DialogContent from '@material-ui/core/DialogContent'; 17 | import DialogActions from '@material-ui/core/DialogActions'; 18 | import Table from '@material-ui/core/Table'; 19 | import TableBody from '@material-ui/core/TableBody'; 20 | import TableCell from '@material-ui/core/TableCell'; 21 | import TableContainer from '@material-ui/core/TableContainer'; 22 | import TableHead from '@material-ui/core/TableHead'; 23 | import TableRow from '@material-ui/core/TableRow'; 24 | import Paper from '@material-ui/core/Paper'; 25 | 26 | const customStyle = { 27 | color: 'white', 28 | margin: '5px', 29 | padding: '5px', 30 | } 31 | 32 | 33 | const customStyle2 = { 34 | color: 'rgb(0, 255, 0)', 35 | margin: '5px', 36 | padding: '5px', 37 | } 38 | 39 | export default function NodeDialog(props) { 40 | 41 | // Utiilize React Hooks to handle Opening and Closing of dialog box 42 | const [open, setOpen] = React.useState(false); 43 | 44 | const handleClickOpen = () => { 45 | setOpen(true); 46 | }; 47 | const handleClose = () => { 48 | setOpen(false); 49 | }; 50 | 51 | // Deconstruct node from props - Passed down for CurrentNode.js 52 | const { node } = props; 53 | 54 | // Initialize address array to hold all Table Rows & Cells for each address 55 | const addressArr = []; 56 | 57 | // Iterate of addresses and push a row & cell for each one 58 | if (node.addresses){ 59 | node.addresses.forEach((address, ind) => { 60 | addressArr.push( 61 | 62 | 63 | {address.type}: 64 | 65 | {address.address} 66 | 67 | ) 68 | }) 69 | } 70 | 71 | 72 | return ( 73 |
74 | 77 | 83 | 84 |
Current Node Info
85 |
86 | 87 | 88 | 89 | 90 | 91 | Key 92 | Value 93 | 94 | 95 | 96 | 97 | 98 | Node Name: 99 | 100 | {node.name} 101 | 102 | 103 | 104 | Hostname: 105 | 106 | {node.hostname} 107 | 108 | 109 | 110 | Operating System: 111 | 112 | {node.os} 113 | 114 | 115 | 116 | Created: 117 | 118 | {node.created} 119 | 120 | 121 | 122 | Unique ID: 123 | 124 | {node.uid} 125 | 126 | 127 | {addressArr} 128 | 129 | 130 | 131 | Allocatable CPU: 132 | 133 | {node.allocatable.cpu} 134 | 135 | 136 | 137 | Allocatable Ephemeral-Storage: 138 | 139 | {node.allocatable["ephemeral-storage"]} 140 | 141 | 142 | 143 | Allocatable Ephemeral-Storage: 144 | 145 | {node.allocatable["ephemeral-storage"]} 146 | 147 | 148 | 149 | Allocatable memory: 150 | 151 | {node.allocatable.memory} 152 | 153 | 154 | 155 | Allocatable pods: 156 | 157 | {node.allocatable.pods} 158 | 159 | 160 | 161 | 162 |
163 |
164 |
165 | 166 | 169 | 170 |
171 |
172 | ); 173 | } -------------------------------------------------------------------------------- /client/Dialog/PodsDialog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************ 3 | * 4 | * @module PodsDialog.js 5 | * @author team Kubermetrics 6 | * @date 7 | * @description Dialog Box for Pod Element 8 | * 9 | * ************************************ 10 | */ 11 | import * as React from 'react'; 12 | import Button from '@material-ui/core/Button'; 13 | import { } from '@material-ui/core/styles'; 14 | import Dialog from '@material-ui/core/Dialog'; 15 | import DialogTitle from '@material-ui/core/DialogTitle'; 16 | import DialogContent from '@material-ui/core/DialogContent'; 17 | import DialogActions from '@material-ui/core/DialogActions'; 18 | import Table from '@material-ui/core/Table'; 19 | import TableBody from '@material-ui/core/TableBody'; 20 | import TableCell from '@material-ui/core/TableCell'; 21 | import TableContainer from '@material-ui/core/TableContainer'; 22 | import TableHead from '@material-ui/core/TableHead'; 23 | import TableRow from '@material-ui/core/TableRow'; 24 | import Paper from '@material-ui/core/Paper'; 25 | 26 | const customStyle = { 27 | color: 'white', 28 | margin: '5px', 29 | padding: '5px', 30 | } 31 | 32 | export default function PodDialog(props) { 33 | 34 | // Utilize React Hooks to handle opening and closing of dialog box 35 | const [open, setOpen] = React.useState(false); 36 | 37 | const handleClickOpen = () => { 38 | setOpen(true); 39 | }; 40 | const handleClose = () => { 41 | setOpen(false); 42 | }; 43 | 44 | // Deconstruct pod passed down from Pod Element 45 | const { pod } = props; 46 | // Initialize container array to hold all Table Rows for each container 47 | const containerArr = []; 48 | 49 | if (pod.containers){ 50 | pod.containers.forEach((container, ind) => { 51 | containerArr.push( 52 | 53 | 54 | Container {ind + 1}: 55 | 56 | {container.image} 57 | 58 | ) 59 | }) 60 | } 61 | 62 | 63 | 64 | return ( 65 |
66 | 69 | 75 | 76 |
Pod Info
77 |
78 | 79 | 80 | 81 | 82 | 83 | Key 84 | Value 85 | 86 | 87 | 88 | 89 | 90 | Pod Name: 91 | 92 | {pod.podName} 93 | 94 | 95 | 96 | Api Version: 97 | 98 | {pod.apiVersion} 99 | 100 | 101 | 102 | Node Name: 103 | 104 | {pod.nodeName} 105 | 106 | 107 | 108 | Namespace: 109 | 110 | {pod.namespace} 111 | 112 | 113 | 114 | Created: 115 | 116 | {pod.created} 117 | 118 | 119 | 120 | Service Account: 121 | 122 | {pod.serviceAccount} 123 | 124 | 125 | 126 | Service Account Name: 127 | 128 | {pod.serviceAccountName} 129 | 130 | 131 | 132 | Host IP: 133 | 134 | {pod.hostIP} 135 | 136 | 137 | 138 | Pod IP: 139 | 140 | {pod.podIP} 141 | 142 | {containerArr} 143 | 144 |
145 |
146 |
147 | 148 | 151 | 152 |
153 |
154 | ); 155 | } -------------------------------------------------------------------------------- /client/Dialog/ServiceDialog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************ 3 | * 4 | * @module ServiceDialog.js 5 | * @author team Kubermetrics 6 | * @date 7 | * @description Dialog Box for Service Element 8 | * 9 | * ************************************ 10 | */ 11 | import * as React from 'react'; 12 | import Button from '@material-ui/core/Button'; 13 | import { } from '@material-ui/core/styles'; 14 | import Dialog from '@material-ui/core/Dialog'; 15 | import DialogTitle from '@material-ui/core/DialogTitle'; 16 | import DialogContent from '@material-ui/core/DialogContent'; 17 | import DialogActions from '@material-ui/core/DialogActions'; 18 | import Table from '@material-ui/core/Table'; 19 | import TableBody from '@material-ui/core/TableBody'; 20 | import TableCell from '@material-ui/core/TableCell'; 21 | import TableContainer from '@material-ui/core/TableContainer'; 22 | import TableHead from '@material-ui/core/TableHead'; 23 | import TableRow from '@material-ui/core/TableRow'; 24 | import Paper from '@material-ui/core/Paper'; 25 | 26 | const customStyle = { 27 | color: 'white', 28 | margin: '5px', 29 | padding: '5px', 30 | } 31 | 32 | 33 | export default function ServiceDialog(props) { 34 | 35 | // Utilize React Hooks to handle opening and closing of dialog box 36 | 37 | const [open, setOpen] = React.useState(false); 38 | 39 | const handleClickOpen = () => { 40 | setOpen(true); 41 | }; 42 | const handleClose = () => { 43 | setOpen(false); 44 | }; 45 | 46 | // Deconstruct service passed down from Service Element 47 | const { service } = props; 48 | 49 | // Initialize ports Array that will contain all Table Rows & Cells for each port contained in the service 50 | const portsArr = []; 51 | 52 | service.allData.spec.ports.forEach((port, ind) => { 53 | portsArr.push( 54 | <> 55 | 56 | 57 | P{ind + 1} Port Name: 58 | 59 | {port.name} 60 | 61 | 62 | 63 | P{ind + 1} Port: 64 | 65 | {port.port} 66 | 67 | 68 | 69 | P{ind + 1} Protocol: 70 | 71 | {port.protocol} 72 | 73 | 74 | ) 75 | }) 76 | 77 | return ( 78 |
79 | 82 | 88 | 89 |
Service Info
90 |
91 | 92 | 93 | 94 | 95 | 96 | Key 97 | Value 98 | 99 | 100 | 101 | 102 | 103 | Service Name: 104 | 105 | {service.name} 106 | 107 | 108 | 109 | Type: 110 | 111 | {service.type} 112 | 113 | 114 | 115 | UID: 116 | 117 | {service.id} 118 | 119 | 120 | 121 | Created: 122 | 123 | {service.created} 124 | 125 | 126 | 127 | Cluster IP: 128 | 129 | {service.allData.spec.clusterIP} 130 | 131 | {portsArr} 132 | 133 |
134 |
135 |
136 | 137 | 140 | 141 |
142 |
143 | ); 144 | } -------------------------------------------------------------------------------- /client/actions/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************ 3 | * 4 | * @module actions.js 5 | * @author team Kubermetrics 6 | * @date 7 | * @description Action functions 8 | * 9 | * ************************************ 10 | */ 11 | 12 | 13 | import axios from 'axios'; 14 | import * as actionTypes from '../constants/actionTypes'; 15 | 16 | /* 17 | All Action Functions That Go To Reducer 18 | */ 19 | 20 | export const getNamespaceList = namespaceList => ({ 21 | type: actionTypes.GET_NAMESPACELIST, 22 | payload: namespaceList, 23 | }); 24 | 25 | export const getPods = podsList => ({ 26 | type: actionTypes.GET_PODS, 27 | payload: podsList, 28 | }); 29 | 30 | export const getNodes = nodesList => ({ 31 | type: actionTypes.GET_NODES, 32 | payload: nodesList 33 | }); 34 | 35 | export const getDeployments = deploymentList => ({ 36 | type: actionTypes.GET_DEPLOYMENTS, 37 | payload: deploymentList 38 | }); 39 | 40 | export const changeNode = node => ({ 41 | type: actionTypes.CHANGE_NODE, 42 | payload: node 43 | }) 44 | export const getServices = serviceList => ({ 45 | type: actionTypes.GET_SERVICES, 46 | payload: serviceList 47 | }); 48 | 49 | export const changeNamespace = namespace => ({ 50 | type: actionTypes.CHANGE_NAMESPACE, 51 | payload: namespace 52 | }) 53 | 54 | export const getIngress = ingressList => ({ 55 | type: actionTypes.GET_INGRESS, 56 | payload: ingressList, 57 | }); 58 | 59 | 60 | /* 61 | All Functions That Make API Calls To Server To Fetch Pods, Deployments, Services, Ingresses & Nodes, 62 | Once we receive the array from the server, we iterate over it and create object we will then send to reducers via 63 | the action functions. 64 | */ 65 | 66 | 67 | export const fetchPods = async (url = '/podList') => { 68 | 69 | let response = await axios.get(url); 70 | let podsList = []; 71 | 72 | 73 | 74 | response.data.items.forEach((item) => { 75 | podsList.push({ 76 | allData: item, 77 | apiVersion: response.data.apiVersion, 78 | nodeName: item.spec.nodeName, 79 | label: item.metadata.labels.app, 80 | podName: item.metadata.name, 81 | namespace: item.metadata.namespace, 82 | uid: item.metadata.uid, 83 | created: item.metadata.creationTimestamp, 84 | containers: item.spec.containers, 85 | serviceAccount: item.spec.serviceAccount, 86 | serviceAccountName: item.spec.serviceAccountName, 87 | hostIP: item.status.hostIP, 88 | podIP: item.status.podIP, 89 | phase: item.status.phase 90 | }) 91 | }) 92 | 93 | return podsList; 94 | 95 | }; 96 | 97 | 98 | export const fetchIngress = async (url = '/ingressList') => { 99 | 100 | const response = await axios.get(url); 101 | 102 | const { items } = response.data; 103 | const { metadata, spec } = items[0]; 104 | 105 | const ingressList = [{ 106 | metadata: { 107 | class: metadata.annotations['kubernetes.io/ingress.class'], 108 | creationTime: metadata.creationTimestamp, 109 | name: metadata.name, 110 | namespace: metadata.namespace, 111 | uid: metadata.uid, 112 | }, 113 | host: spec.rules[0].host, 114 | paths: spec.rules[0].http.paths.map((path) => ({ 115 | pathType: path.pathType, 116 | serviceName: path.backend.serviceName, 117 | servicePort: path.backend.servicePort, 118 | path: path.path, 119 | })), 120 | fullData: items, 121 | }]; 122 | 123 | return ingressList; 124 | 125 | }; 126 | 127 | 128 | export const fetchNodes = async (url = '/nodeList') => { 129 | 130 | let response = await axios.get(url); 131 | let nodeList = []; 132 | 133 | response.data.items.forEach((item) => { 134 | 135 | const { metadata, status } = item; 136 | const { labels } = metadata; 137 | 138 | 139 | nodeList.push({ 140 | 141 | allData: item, 142 | created: metadata.creationTimestamp, 143 | arch: labels['kubernetes.io/arch'], 144 | os: labels['kubernetes.io/os'], 145 | hostname: labels['kubernetes.io/hostname'], 146 | managedFields: metadata.managedFields, 147 | name: metadata.name, 148 | resourceVersion: metadata.resourceVersion, 149 | uid: metadata.uid, 150 | addresses: status.addresses, 151 | allocatable: status.allocatable, 152 | capacity: status.capacity, 153 | conditions: status.conditions, 154 | daemonEndpoints: status.daemonEndpoints, 155 | images: status.images, 156 | nodeInfo: status.nodeInfo, 157 | 158 | }) 159 | }); 160 | 161 | return nodeList; 162 | 163 | } 164 | 165 | export const fetchDeployments = async (url = '/deploymentList') => { 166 | 167 | let response = await axios.get(url); 168 | let deploymentList = []; 169 | 170 | response.data.items.forEach((item) => { 171 | 172 | const { metadata, spec, status } = item; 173 | 174 | deploymentList.push({ 175 | 176 | allData: item, 177 | created: metadata.creationTimestamp, 178 | managedFields: metadata.managedFields, 179 | name: metadata.name, 180 | namespace: metadata.namespace, 181 | uid: metadata.uid, 182 | replicas: spec.replicas, 183 | selector: spec.matchLabels, 184 | strategy: spec.strategy, 185 | template: spec.template, 186 | availabeReplicas: status.availabeReplicas, 187 | conditions: status.conditions, 188 | readyReplicas: status.readyReplicas 189 | 190 | }) 191 | }); 192 | 193 | return deploymentList; 194 | 195 | } 196 | 197 | export const fetchServices = async (url = '/serviceList') => { 198 | let response = await axios.get(url); 199 | 200 | let servicesList = []; 201 | 202 | response.data.items.forEach((item) => { 203 | 204 | servicesList.push({ 205 | allData: item, 206 | created: item.metadata.creationTimestamp, 207 | name: item.metadata.name, 208 | namespace: item.metadata.namespace, 209 | id: item.metadata.uid, 210 | manager: item.metadata.managedFields.manager, 211 | labels: item.metadata.labels, 212 | selector: item.spec.selector, 213 | type: item.spec.type 214 | 215 | }) 216 | 217 | }); 218 | 219 | return servicesList; 220 | } 221 | 222 | 223 | export const fetchNamespaces = async (url = '/namespaceList') => { 224 | let response = await axios.get(url); 225 | 226 | let namespaceList = []; 227 | 228 | response.data.items.forEach((item) => { 229 | 230 | namespaceList.push({ 231 | allData: item, 232 | name: item.metadata.name 233 | }) 234 | 235 | }); 236 | 237 | return namespaceList 238 | } 239 | 240 | /* 241 | All Custom Fetch Functions that will make a post request to the server with the current namespace 242 | */ 243 | 244 | 245 | export const fetchCustomPods = async (namespace, url = '/customPods') => { 246 | let response = await fetch(url, { 247 | method: 'POST', 248 | headers: { 'Content-Type': 'application/json' }, 249 | body: JSON.stringify({namespace: namespace}) 250 | }) 251 | .then(res => res.json()) 252 | .then(data => data); 253 | 254 | let podsList = []; 255 | 256 | response.items.forEach((item) => { 257 | podsList.push({ 258 | allData: item, 259 | apiVersion: response.apiVersion, 260 | nodeName: item.spec.nodeName, 261 | label: item.metadata.labels.app, 262 | podName: item.metadata.name, 263 | namespace: item.metadata.namespace, 264 | uid: item.metadata.uid, 265 | created: item.metadata.creationTimestamp, 266 | containters: item.spec.containers, 267 | serviceAccount: item.spec.serviceAccount, 268 | serviceAccountName: item.spec.serviceAccountName, 269 | hostIP: item.status.hostIP, 270 | podIP: item.status.podIP, 271 | phase: item.status.phase 272 | }) 273 | }) 274 | return podsList; 275 | } 276 | 277 | export const fetchCustomServices = async (namespace, url = '/customServices') => { 278 | let response = await fetch(url, { 279 | method: 'POST', 280 | headers: { 'Content-Type': 'application/json' }, 281 | body: JSON.stringify({namespace: namespace}) 282 | }) 283 | .then(res => res.json()) 284 | .then(data => data); 285 | 286 | let servicesList = []; 287 | 288 | response.items.forEach((item) => { 289 | 290 | servicesList.push({ 291 | allData: item, 292 | created: item.metadata.creationTimestamp, 293 | name: item.metadata.name, 294 | namespace: item.metadata.namespace, 295 | id: item.metadata.uid, 296 | manager: item.metadata.managedFields.manager, 297 | labels: item.metadata.labels, 298 | selector: item.spec.selector, 299 | type: item.spec.type 300 | }) 301 | }); 302 | return servicesList; 303 | } 304 | 305 | export const fetchCustomDeployments = async (namespace, url = '/customDeployments') => { 306 | let response = await fetch(url, { 307 | method: 'POST', 308 | headers: { 'Content-Type': 'application/json' }, 309 | body: JSON.stringify({namespace: namespace}) 310 | }) 311 | .then(res => res.json()) 312 | .then(data => data); 313 | 314 | let deploymentList = []; 315 | 316 | response.items.forEach((item) => { 317 | 318 | const { metadata, spec, status } = item; 319 | 320 | deploymentList.push({ 321 | 322 | allData: item, 323 | created: metadata.creationTimestamp, 324 | managedFields: metadata.managedFields, 325 | name: metadata.name, 326 | namespace: metadata.namespace, 327 | uid: metadata.uid, 328 | replicas: spec.replicas, 329 | selector: spec.matchLabels, 330 | strategy: spec.strategy, 331 | template: spec.template, 332 | availabeReplicas: status.availabeReplicas, 333 | conditions: status.conditions, 334 | readyReplicas: status.readyReplicas 335 | 336 | }) 337 | }); 338 | return deploymentList; 339 | } 340 | -------------------------------------------------------------------------------- /client/assets/kubermetrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kubermetrics/fc881ab63c260fd04d2e9383c07b47e103e64e27/client/assets/kubermetrics.png -------------------------------------------------------------------------------- /client/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kubermetrics/fc881ab63c260fd04d2e9383c07b47e103e64e27/client/assets/logo.png -------------------------------------------------------------------------------- /client/components/Deployments/Deployment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************ 3 | * 4 | * @module Deployment.js 5 | * @author team Kubermetrics 6 | * @date 7 | * @description React Component for Deployments 8 | * 9 | * ************************************ 10 | */ 11 | import React from 'react'; 12 | import CardContent from '@material-ui/core/CardContent'; 13 | import Button from '@material-ui/core/Button'; 14 | import './Deployment.style.css'; 15 | import * as mui from '../mui-elements' 16 | import { connect } from 'react-redux'; 17 | import * as actions from '../../actions/actions'; 18 | import DeploymentDialog from '../../Dialog/DeploymentDialog'; 19 | 20 | // Map dispatch to props to allow ability to refresh Deployments list from within component. 21 | 22 | const mapDispatchToProps = dispatch => ({ 23 | loadDeployments: async (namespace) => { 24 | // fetch deployments & format data with current namespace. 25 | let deployments = await actions.fetchCustomDeployments(namespace); 26 | // send array of deployments to reducer. 27 | dispatch(actions.getDeployments(deployments)) 28 | } 29 | }) 30 | 31 | // Map state to props so we know the currently selected namespace. 32 | 33 | const mapStateToProps = state => ({ 34 | namespace: state.namespaces.currentNamespace 35 | }) 36 | 37 | 38 | 39 | 40 | const Deployment = (props) => { 41 | 42 | // Deconstruct deployment element from mui file 43 | 44 | const { DeploymentElement } = mui; 45 | 46 | // Conditional render accounting for the case in which no running deployments are found. 47 | 48 | if (props.notLoaded) { 49 | return ( 50 |
51 | 52 | 53 |
54 |

No Deployments found

55 |
56 |
57 |
58 |
59 |
60 | ) 61 | } 62 | 63 | // Default Render displaying Deploment name & deployment dialog to handle extra data. 64 | 65 | return ( 66 |
67 | 68 | 69 |
70 |

{props.deployment.name}

71 |
72 |
73 |
74 |
75 |
76 | ) 77 | } 78 | 79 | // Export component as well as connect it to Redux Reducers & State 80 | 81 | export default connect(mapStateToProps, mapDispatchToProps)(Deployment); 82 | -------------------------------------------------------------------------------- /client/components/Deployments/Deployment.style.css: -------------------------------------------------------------------------------- 1 | .podContainer { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | filter: drop-shadow(-1px 6px 3px rgba(0, 0, 0, 0.5)); 7 | width: 300px; 8 | 9 | } 10 | .podContainer:hover { 11 | 12 | filter: 13 | drop-shadow(5px 0px 0px rgb(143, 92, 254)) 14 | drop-shadow(-5px 0px 0px rgb(143, 92, 254)) 15 | drop-shadow(0px -5px 0px rgb(143, 92, 254)) 16 | drop-shadow(0px 5px 0px rgb(143, 92, 254)) 17 | 18 | } 19 | 20 | 21 | p { 22 | margin: 2px; 23 | text-align: center; 24 | text-shadow: 2px 2px 4px #000000; 25 | } 26 | .podLabel { 27 | font-size: 18px; 28 | margin-top: 5px; 29 | } 30 | -------------------------------------------------------------------------------- /client/components/Deployments/DeploymentList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************ 3 | * 4 | * @module DeploymentList.js 5 | * @author team Kubermetrics 6 | * @date 7 | * @description React Component that will contain all Deployments 8 | * 9 | * ************************************ 10 | */ 11 | import React from 'react'; 12 | import { connect } from 'react-redux'; 13 | import Deployment from './Deployment'; 14 | import './DeploymentList.style.css' 15 | 16 | 17 | // Map deployment array to props 18 | 19 | const mapStateToProps = state => ({ 20 | deployments: state.deployments.deployments 21 | }) 22 | 23 | 24 | const DeploymentList = (props) => { 25 | 26 | // Array that will contain all Deployment react components 27 | const deploymentsArray = []; 28 | 29 | // Replicas will count each deployments replicas and dispay for end user 30 | let replicas = 0; 31 | 32 | // Iterate over array and create react element for each deployment 33 | props.deployments.forEach((deployment, ind) => { 34 | deploymentsArray.push() 38 | // Add up replicas 39 | replicas += deployment.replicas; 40 | }) 41 | 42 | // Conditional Render to handle case in which no running deployments are found. 43 | 44 | if (!deploymentsArray.length){ 45 | return ( 46 |
47 |
48 |

Deployments

49 |

None Found in Current Namespace

50 |
51 |
52 | 53 |
54 |
55 | ) 56 | } 57 | 58 | // Default Render to return Deployment container with all deployment elements 59 | 60 | return ( 61 | 62 |
63 |
64 |

Deployments

65 |

Total Replicas: {replicas}

66 |
67 |
68 | {deploymentsArray} 69 |
70 |
71 | ) 72 | } 73 | 74 | export default connect(mapStateToProps, null)(DeploymentList) -------------------------------------------------------------------------------- /client/components/Deployments/DeploymentList.style.css: -------------------------------------------------------------------------------- 1 | .deploymentList { 2 | /* width: 75vw; */ 3 | background-color: #222426; 4 | margin: 10px; 5 | margin-left: 15px; 6 | width: auto; 7 | height: 330px; 8 | border-radius: 10px; 9 | max-width: 1300px; 10 | min-width: 1300px; 11 | border: 1px solid rgb(19, 19, 19); 12 | 13 | 14 | display: flex; 15 | flex-direction: column; 16 | -webkit-box-shadow: 5px 5px 15px 5px rgba(0,0,0,0.67); 17 | box-shadow: 5px 5px 15px 5px rgba(0,0,0,0.67); 18 | } -------------------------------------------------------------------------------- /client/components/GrafanaMonitoring/ClusterDashboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************ 3 | * 4 | * @module ClusterDashboard.js 5 | * @author team Kubermetrics 6 | * @date 7 | * @description Create iframe to connect to grafana instance 8 | * 9 | * ************************************ 10 | */ 11 | import React from 'react' 12 | 13 | 14 | class ClusterDashboard extends React.Component { 15 | render() { 16 | //suppose user is received from props 17 | const { user } = this.props 18 | return ( 19 | 20 |