├── .gitignore ├── LICENSE ├── README.md ├── __tests__ ├── react.test.tsx └── super.test.tsx ├── babel.config.testing.js ├── build └── mock_data.json ├── jest.config.ts ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── SentryBot_sm.png ├── graph_background.png ├── kubesentrylogo.png └── node-relationship.png ├── scripts ├── setup.js ├── test.js └── test_connection.js ├── server ├── AlertLogs.txt ├── Controllers │ ├── kubeMetrics.ts │ ├── logsController.ts │ └── promMetrics.ts ├── Models │ ├── k8sModel.ts │ └── prometheusModel.ts ├── Routers │ └── metricsRouter.ts ├── index.d.ts ├── server.ts ├── tsconfig.json └── types │ └── server-types.ts ├── src ├── app │ ├── api │ │ ├── graph │ │ │ └── route.ts │ │ ├── podStatus │ │ │ └── route.ts │ │ └── promQuery │ │ │ └── route.ts │ ├── charts │ │ ├── LinePlot.tsx │ │ ├── PieChart.tsx │ │ └── TimeSeriesPlot.tsx │ ├── components │ │ ├── Banner.tsx │ │ ├── GraphVis.tsx │ │ ├── GraphVisOptions.ts │ │ ├── Searchbar.tsx │ │ └── Tooltip.tsx │ ├── counter.tsx │ ├── dashboard │ │ ├── about │ │ │ └── page.tsx │ │ ├── alert │ │ │ ├── api │ │ │ │ └── route.ts │ │ │ └── page.tsx │ │ ├── graph │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── pods │ │ │ └── page.tsx │ │ ├── report │ │ │ └── page.tsx │ │ ├── resource │ │ │ └── page.tsx │ │ └── status │ │ │ └── page.tsx │ ├── error.tsx │ ├── layout.tsx │ ├── not-found.tsx │ ├── page.tsx │ ├── podapi │ │ └── route.ts │ ├── styles │ │ ├── error.css │ │ ├── globals.css │ │ └── graph.css │ └── ui │ │ ├── AlertCard.tsx │ │ ├── PodCard.tsx │ │ ├── PodModal.tsx │ │ └── sidebar.tsx └── util │ └── utils.ts ├── tailwind.config.js ├── tsconfig.json └── types ├── cytoscape-core.d.ts ├── cytoscape-cose-bilkent.d.ts ├── domTypes.tsx ├── react-graph-vis.d.ts └── types.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | .next 4 | 5 | *.png~ 6 | 7 | build/ 8 | node_modules/ 9 | venv/ 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Alt textAlt text 2 | Alt text 3 | Alt text 4 | Alt text 5 | Alt text 6 | Alt text 7 | Alt text 8 | Alt text 9 | Alt text 10 | Alt text 11 | Alt text 12 | Alt text 13 | Alt text 14 | 15 | 16 | 17 | 18 | # Kube Sentry 19 | 20 | Kube Sentry an innovative open-source tool reshaping Kubernetes cluster management. Designed for developers, it offers advanced features for early detection of potential issues, aiming to notably reduce bug occurrences. Seamlessly integrated with Prometheus, it enhances tracking and alerting capabilities, providing a holistic, user-friendly approach to Kubernetes management. 21 | 22 | 23 | ## Quickstart 24 | Clone this repository, install dependencies and run: 25 | 26 | ``` 27 | git clone https://github.com/oslabs-beta/KubeSentry.git 28 | cd KubeSentry 29 | npm install 30 | npm run 31 | ``` 32 | 33 | 34 | The Sentry Dashboard will be available at [localhost:3000](http://localhost:3000/dashboard). 35 | 36 | # Setting up your Kubernetes cluster 37 | 38 | - Install kubectl. Run `kubectl version` to check if it has been sucessfully downloaded. 39 | 40 | - KubeSentry will connect to the Kubernetes cluster configured by `kubectl config current-context`. For testing purposes, you can run a local Kubernetes cluster either with [Docker Desktop](https://www.docker.com/products/docker-desktop/) or [minikube](https://minikube.sigs.k8s.io/docs/start/). 41 | 42 | * The following commands will follow managing your K8s cluster on Docker Desktop. 43 | - To start Kubernetes on docker-desktop, navigate to Settings > Kubernetes and **Enable Kubernetes**. 44 | - run `kubectl cluster-config` to ensure your Kubernetes cluster is operating. 45 | 46 | # Set up metrics server 47 | 48 | - [Kubernetes Metrics Server](https://github.com/kubernetes-sigs/metrics-server) is required to get additional information from your K8s cluster. 49 | - Follow instructions on the Github repo to install the Kubernetes metrics-server. 50 | - ** Note the `--kubelet-insecure-tls` flag to bypass the CA authenication for kubelets. ** 51 | ```yaml 52 | spec: 53 | containers: 54 | - args: 55 | - --cert-dir=/etc/metrics-server-auth 56 | - --secure-port=4443 57 | - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname 58 | - --kubelet-use-node-status-port 59 | - --metric-resolution=15s 60 | - --kubelet-insecure-tls 61 | image: registry.k8s.io/metrics-server/metrics-server:v0.6.4 62 | ``` 63 | - If your metrics server is installed correctly, you will be able to run `kubectl top pods` and `kubectl top nodes` to get pod and node metrics of your cluster. 64 | 65 | # Configure Prometheus to collect cluster metrics 66 | 67 | If you have not already, install [Helm](https://helm.sh/). Helm is a repository of published yaml files that are used to configure deployments used in Kubernetes. 68 | 69 | Add the Prometheus Helm chart to your Helm repo 70 | `helm repo add prometheus-community https://prometheus-community.github.io/helm-charts` 71 | 72 | - you can check if the Helm chart has been sucessfully added by running `helm list` in you CLI 73 | 74 | Install Prometheus to your Kubernetes cluster. 75 | `helm install prometheus prometheus-community/prometheus` 76 | 77 | - If you run `kubectl get all` you should see the deployments, configmaps, services Prometheus has installed. 78 | - If you are running Kubernetes on docker-desktop, you will not be able to current access the Prometheus server API that is hosted on port 9090. This is because we are running Kubernetes in a containerized environment. To work around this, we will open up a **nodeport** service that will act as a middle man to set up a connection with the prometheus server with the outside world 79 | - `kubectl apply -f https://github.com/oslabs-beta/KubeSentry/Yamlfiles/prometheus-nodeport.yaml` 80 | - Now you should be able to access the Prometheus server API via `localhost:31302` 81 | 82 | # instrument your application 83 | 84 | To instrument your appplication for Prometheus to scrape, you need to require [Prom-client](https://github.com/siimon/prom-client) onto your application. Prometheus' default scraping endpoint is `/metrics` 85 | 86 | ```js 87 | const client = require('prom-client'); 88 | 89 | //create a custom counter 90 | const customCounter = new client.Counter({ 91 | name: 'my_custom_counter', 92 | help: 'Description of my custom counter', 93 | }); 94 | 95 | //increment counter 96 | customCounter.inc(); 97 | 98 | //endpoint to provide metrics for prometheus to scrape 99 | app.get('/metrics', async (req, res) => { 100 | const data = await client.register.metrics(); 101 | res.header('Content-Type', 'text/plain').status(200).send(data); 102 | }); 103 | ``` 104 | 105 | As an alternative, you can install [express-prom-bundle](https://www.npmjs.com/package/express-prom-bundle) which grants you the **metricsMiddleware** to expose all your metrics to the /metrics endoint with one line. 106 | 107 | ```js 108 | const promBundle = require('express-prom-bundle'); 109 | const client = require('prom-client'); 110 | const metricsMiddleware = promBundle({ includePath: true }); 111 | 112 | app.use(metricsMiddleware); 113 | ``` 114 | 115 | # Create an image of your application 116 | 117 | Build an image of your application to be deployed on Kubernetes. 118 | 119 | # Deploy your application on Kubernetes 120 | 121 | Create a deployment yaml file to deploy your application. This will handle any horizontal scaling required for your app. 122 | 123 | > Example of a deployment configuration yaml file 124 | 125 | ```yaml 126 | apiVersion: apps/v1 127 | kind: Deployment 128 | metadata: 129 | name: sentry-tests 130 | namespace: sentry-tests #optional. Will create in current namespace if left out 131 | spec: 132 | selector: 133 | matchLabels: 134 | app: sentry 135 | replicas: 4 136 | template: 137 | metadata: 138 | labels: 139 | app: sentry 140 | spec: 141 | containers: 142 | - name: sentry-test 143 | image: sentrytest4:1.0 #application image 144 | ports: 145 | - containerPort: 3000 146 | ``` 147 | 148 | Run `kubectl apply -f path/to/deployment.yaml` to deploy your application. 149 | 150 | Create a service yaml configuration to handle load balancing for your applications: 151 | 152 | ```yaml 153 | apiVersion: v1 154 | kind: Service 155 | metadata: 156 | name: sentrytest-nodeport 157 | spec: 158 | selector: 159 | app: sentry #must match the label of the pod 160 | ports: 161 | - name: web 162 | port: 3000 #port to access the service node 163 | targetPort: 3000 #must match the container port of the application 164 | nodePort: 30011 #optional nodePort for open access if running in a docker conatiner 165 | type: NodePort 166 | ``` 167 | 168 | Run `kubectl apply -f path/to/service.yaml` to deploy your application. 169 | 170 | Now you should be able to access your application at `localhost:30011` 171 | 172 | # Configure Prometheus to scrape metrics 173 | 174 | Add scrape jobs for your application and the metrics server. Learn more at [Prometheus](https://prometheus.io/docs/prometheus/latest/getting_started/) 175 | 176 | ```yaml 177 | global: 178 | evaluation_interval: 15s 179 | scrape_interval: 10s 180 | scrape_timeout: 10s 181 | rule_files: 182 | - /etc/config/recording_rules.yml 183 | - /etc/config/alerting_rules.yml 184 | - /etc/config/rules 185 | - /etc/config/alerts 186 | - /etc/prometheus/rules/*.yaml #will be used later for 187 | scrape_configs: 188 | - job_name: 'kubernetes-metrics' 189 | static_configs: 190 | - targets: ['metrics-server.kube-system.svc.cluster.local:443'] 191 | - job_name: 'sentry-test' 192 | static_configs: 193 | - targets: ['sentrytest-nodeport.default.svc.cluster.local:3000'] 194 | - job_name: prometheus 195 | static_configs: 196 | - targets: [localhost:9090] 197 | ``` 198 | 199 | # The Kube Sentry 200 | 201 | ``` 202 | git clone 203 | npm install 204 | npm run 205 | ``` 206 | http://localhost:3000/dashboard should display your Kubernetes cluster. 207 | 208 | # Set up the Alert Manager 209 | 210 | Coming soon! 211 | 212 | # Learn More 213 | 214 | Head over to kubesentry.app! 215 | 216 | -------------------------------------------------------------------------------- /__tests__/react.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | 6 | import React from 'react' 7 | import { PodCard, PodCardProps } from "../src/app/ui/PodCard"; 8 | import '@testing-library/jest-dom'; 9 | import { render, screen, waitFor, fireEvent } from '@testing-library/react'; 10 | 11 | describe('Unit testing React components', () => { 12 | describe('PodCard', () => { 13 | const mockOnClick = jest.fn(); 14 | const mockPodData: PodCardProps = { 15 | podName: 'coredns-5dd5756b68-425cd', 16 | podStatus: 'Running', 17 | nameSpace: 'kube-system', 18 | handleClick: mockOnClick, 19 | } 20 | 21 | test('PodCard displays Name, Status, and Namespace', () => { 22 | render( ) 23 | const podCard = screen.getByRole('article') 24 | expect(podCard).toHaveTextContent(mockPodData.podName) 25 | expect(podCard).toHaveTextContent(mockPodData.podStatus) 26 | expect(podCard).toHaveTextContent(mockPodData.nameSpace) 27 | }); 28 | 29 | test('PodCard delete button can be clicked', () => { 30 | render( ) 31 | const podCard = screen.getByRole('article') 32 | const deleteButton = screen.getByRole('button', {name: /Delete/i}) 33 | expect(podCard).toContainElement(deleteButton); 34 | fireEvent.click(deleteButton) 35 | expect(mockOnClick).toHaveBeenCalled() 36 | }); 37 | }); 38 | }); 39 | 40 | 41 | -------------------------------------------------------------------------------- /__tests__/super.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment node 3 | */ 4 | 5 | 6 | import supertest from 'supertest' 7 | import { Chance } from 'chance' 8 | import app from '../server/server' 9 | 10 | // Test for error handler 11 | describe('404 error handler', () => { 12 | test('should respond with a 404 status code for unknown API route', async () => { 13 | const res = await supertest(app).get('/non-existent-request') 14 | expect(res.statusCode).toEqual(404) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /babel.config.testing.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', { targets: { node: 'current' } }], 4 | '@babel/preset-typescript', 5 | '@babel/preset-react', 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /build/mock_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "cpu_usage": [ 3 | 43.90120677153551, 51.25960938542985, 45.73992640679415, 54.875713426557425, 4 | 90.90399483490144, 45.49770047626121, 44.13587169934022, 49.29475009393444, 5 | 49.78905498912977, 46.23160887812599, 61.01702532415311, 53.71745718608539, 6 | 86.93746623964488, 49.566671564487635, 54.391770498809954, 7 | 53.63779226306027, 50.57523559215174, 58.00712286735645, 54.196646405360056, 8 | 53.86069469873802, 51.52958374706295, 46.250982406486756, 44.83841926797821, 9 | 52.85917215420686, 49.118749770220184, 46.38551466486374, 41.9286708450927, 10 | 59.015506931414315, 55.03637604987579, 53.128772313158, 46.56706670317584, 11 | 53.943224994198175, 99.68009261612782, 50.46799572003921, 99.98550751393854, 12 | 49.6044586524997, 43.71266947415342, 53.47064014304595, 54.65146827131086, 13 | 43.99945084908892, 50.87646541941701, 46.12437935328111, 45.99438621006001, 14 | 55.34610809340895, 52.637693084061645, 50.24115580169098, 49.48162189537524, 15 | 58.692333182324404, 99.89488533229641, 40.37635084542595, 16 | 44.799702721120454, 47.53092969449587, 43.54745956419991, 46.41417920834611, 17 | 55.77950075848089, 54.22828868909374, 55.303590278661304, 48.03713678301188, 18 | 51.68103469596908, 51.78935822930121, 56.76704323972059, 57.095655205925, 19 | 50.204062050150576, 43.054714330297095, 48.86070069447189, 100.0, 20 | 51.68948284746543, 52.63462229960787, 54.588923245466916, 49.47431453733232, 21 | 51.76037465294249, 55.73849193511951, 52.140982330952035, 40.20879204196221, 22 | 45.750538806684176, 51.478224630353445, 47.38848530511329, 23 | 50.83709828002105, 50.486148543118695, 50.23611063803384, 48.85644337206515, 24 | 51.90411222277849, 46.02114739519948, 54.798858753649434, 44.07188205075656, 25 | 48.22664264255684, 39.56860170274653, 47.4919766341871, 48.23872681207315, 26 | 40.31618302501382, 89.64173310880228, 61.53573898817181, 53.10178057340316, 27 | 43.355192220952645, 48.326192780857504, 53.57285411385168, 28 | 48.38611660627505, 49.181294251061026, 49.110883226234975, 29 | 54.56913557724191, 54.76773287107042, 52.69247357506376, 47.86101162676095, 30 | 46.78807706062108, 48.3831247727119, 92.12798505572066, 42.05070124420156, 31 | 44.328599188856806, 47.07347694471994, 40.558690690093236, 32 | 43.14042613375188, 43.33055376917219, 100.0, 48.61795773467421, 33 | 54.88945129091061, 48.02235047952985, 53.61427828316066, 51.873680954797045, 34 | 58.77826649320363, 52.27188261255532, 47.31440306634142, 47.24206370815352, 35 | 48.32250596090907, 52.15419101586069, 43.561895646969276, 45.13011053908767, 36 | 48.2644748155562, 53.1254427101969, 48.20954359122361, 43.96594938137943, 37 | 44.42971992056204, 44.4146283735635, 51.71418068645948, 51.612162869561054, 38 | 42.69272268990511, 54.22394188118453, 48.533710803522204, 46.40239365071122, 39 | 48.82932288948768, 45.77153705223036, 41.06353593757827, 51.17003967094302, 40 | 52.089683740429656, 51.982005120372925, 44.843357934947484, 41 | 54.85848581622621, 40.66030962533986, 50.51961698456863, 58.851329066054575, 42 | 49.050057229151484, 44.31193051414226, 47.99823791493473, 56.15306584204999, 43 | 62.78341203791083, 48.80593294148086, 93.92120460057473, 47.22392135456454, 44 | 100.0, 61.51912443354138, 59.87972335355256, 44.906187626907155, 45 | 59.09219796562685, 50.96509224553883, 48.101269825054956, 56.87584245682248, 46 | 51.41312213904284, 43.859345080828355, 50.4401053631213, 59.133398211846064, 47 | 44.35196293104676, 100.0, 48.82728812957083, 47.35906374978208, 48 | 53.405087384596584, 41.77988594600172, 44.51648408850953, 49 | 52.557542078403095, 50.575502279874314, 49.4722463054381, 55.21713547302603, 50 | 47.94175473599009, 47.22133969608085, 50.39951655422032, 56.13109967341491, 51 | 54.279031202254, 39.47985758580463, 50.43501380195622, 44.25501233730806, 52 | 57.478828963410685, 45.374079872179294, 44.30053271706114, 53 | 49.304062259715785, 45.30255924745897, 49.16250976226747, 45.70321754878104, 54 | 56.12787735073026, 54.659372056959135, 45.55661005337055, 55 | 47.024622050794974, 50.33414879890628, 53.59581354109039, 49.38262279699563, 56 | 55.845412123271515, 42.38818043294009, 45.40260953750437, 56.52965428005386, 57 | 55.45159015328097, 56.121850645634616, 48.12554091929361, 59.77422803759707, 58 | 52.71809947354611, 46.25904337075189, 56.203753392005275, 47.21870193249034, 59 | 48.4526579825835, 52.00743098189386, 49.89176383203285, 40.92232015854283, 60 | 95.42394234994424, 55.405830970785004, 54.32758675489451, 61 | 50.286496386758564, 59.363755566479654, 53.28997552286957, 62 | 49.27676218822057, 47.975791046403685, 52.50338973245536, 55.20382450120476, 63 | 52.44580132964207, 48.941079093640816, 54.750044817136676, 64 | 58.60335932940023, 54.30435831423634, 48.24075356582683, 44.401930648570094, 65 | 47.19789194062299, 51.77276479739082, 52.70372061085843, 50.296459066635286, 66 | 55.49880709914967, 51.4845482542192, 52.56616324197873, 48.119904677761106, 67 | 46.81467828869964, 53.611992168958594, 42.43304391528875, 44.45418387287494, 68 | 50.67177540976798, 55.34170106244911, 48.121458187113305, 69 | 53.015037635159835, 44.46999595485984, 47.20365011557573, 47.24929085871249, 70 | 50.06810076823691, 45.223479836027174, 41.106046358932474, 71 | 52.948473828071535, 44.65839787388717, 51.258105425107466, 72 | 48.67129775840384, 50.93290320669558, 56.56361362949223, 46.998219195713745, 73 | 51.20721370937089, 47.30781955980671, 42.09226981782575, 51.91818622242673, 74 | 48.04252343059736, 50.78659155387616, 55.93956011306283, 59.43127079692644, 75 | 50.49394481199469, 47.46448109123948, 51.921356574341516, 52.02384935144567, 76 | 47.4016760159364, 51.87395737174346, 49.12197113941704, 48.62813950356003, 77 | 53.186066341724086, 47.03465336369242, 59.72786311618816, 78 | 53.799972469797666, 46.12698885886215, 38.81545150687844, 79 | 49.520822198767384, 47.03943894397459, 47.47586536459326, 80 | 49.544733324251446, 54.02000781770798, 49.48003235572735, 81 | 50.411923509481774, 53.29815106243791, 46.345137982944124, 82 | 52.43756881718544, 49.802526138475486, 46.88546031650091, 56.39950911807624, 83 | 48.818212974395664, 41.266766809889646, 51.58117120049734, 48.0119268479386, 84 | 47.981438620960155, 49.02653882285672, 48.415972316423, 58.599767348784496, 85 | 44.26906663897232, 56.02861069207355, 56.243346487655145, 86 | 50.151780403315136, 55.570816827449306, 47.41743558066639, 87 | 51.49104338826576, 49.30544242332591, 53.54459490875855, 49.767657353727316, 88 | 53.717132993039776, 47.714662813545196, 46.11132125628942, 89 | 53.140112527306755, 100.0, 50.620898089814936, 47.67116734154197, 90 | 47.69918288674419, 48.17695219843566, 51.216389119407744, 46.97493188746599, 91 | 51.098767661224365, 94.13980811498135, 53.55432703572711, 92 | 46.653616466678955, 54.56064274379685, 47.672201508519855, 93 | 54.539248204808786, 53.70556044005574, 100.0, 54.99111108468562, 94 | 50.334429792761135, 46.615618218243135, 33.35499596559827, 95 | 53.73774908806728, 48.54979695356429, 96.0477380447895, 48.52814044781266, 96 | 48.24305960102734, 44.75371264832925, 49.72933721765255, 55.60734065091678, 97 | 44.33414409794335, 50.66179322605252, 54.03523229201597, 49.24033120849106, 98 | 43.59298322494018, 96.75513011893659, 42.16272962819569, 52.96786733832606, 99 | 48.91512701129938, 49.841897006459135, 49.231896088859536, 100 | 55.540565671845116, 56.94656654813983, 41.08465737963625, 55.93945927405011, 101 | 55.35372697647377, 53.4417272286651, 52.390082662252524, 48.849684533535374, 102 | 50.06099605509556, 47.470436839857044, 40.669750696355216, 103 | 53.14963162388425, 100.0, 49.818941995122195, 47.97320464682178, 104 | 42.05659321454119, 62.6383507599584, 55.09457447499106, 100.0, 105 | 52.57002396616755, 52.002883573624395, 45.94763698581057, 106 | 53.102372257597075, 52.361978374489375, 50.49012596262287, 107 | 45.01665526985333, 43.5551659030555, 56.86639124296164, 42.042766234216884, 108 | 57.38008614660417, 55.635123018247306, 60.85465716461176, 46.96638120991636, 109 | 58.37359793194304, 47.31036614084246, 49.298488534768545, 52.3429842110499, 110 | 41.14415872928106, 53.41108001722347, 53.247683408626486, 58.29841680793817, 111 | 56.04617572487447, 49.61871985639005, 50.96067650434665, 48.157053706901685, 112 | 46.630567878661, 48.761252126334846, 42.79151846341522, 55.690005868933554, 113 | 47.48694893326214, 57.622296327548554, 56.08653124225271, 55.36237118179101, 114 | 63.81233351680619, 52.31900224348397, 47.43581983423645, 41.52658207722498, 115 | 46.81313621848056, 54.41998054757647, 50.47493334161545, 100.0, 116 | 46.1717981061756, 51.79742697601963, 42.66242250166411, 53.48410687226456, 117 | 49.05972179811219, 48.21863570130108, 45.88903472423718, 50.19684830069003, 118 | 54.42721870377496, 57.140994382646056, 47.516973016150125, 119 | 45.23997565859422, 46.02893184669188, 51.410800804807515, 90.93564309025606, 120 | 56.44013783744719, 46.2382047786477, 46.030583384802924, 58.8047058015618, 121 | 47.498724384730046, 56.48298062173967, 85.20447970477186, 122 | 43.537056753231454, 55.72350852075416, 50.79426733406959, 123 | 55.981391823239605, 55.00253569417237, 57.803944337975196, 124 | 53.725078367115415, 48.41832485467748, 53.69012030252315, 125 | 41.300046141002774, 49.265295364856286, 45.863218248781884, 126 | 55.57480351514269, 49.85893557728577, 100.0, 52.662605671024096, 127 | 53.2546589405381, 50.64781565062212, 52.70768345652739, 49.63939717683421, 128 | 36.33110905330561, 47.77769640579172, 54.483469401704355, 129 | 46.390930859669666, 49.97146933599883, 44.19665254003334, 130 | 40.918662344677315, 53.4033703446945, 47.09953029561013, 50.78232894125836, 131 | 56.92479810767154, 100.0, 43.386821866861794, 55.29775207655022, 132 | 51.248597709186015, 48.29937592232644, 56.05161388173339, 133 | 47.445838543339974, 44.66405429645384, 46.61834985352981, 134 | 55.579887587196225, 45.94781756424155, 50.5707863283644, 51.189080569351624, 135 | 47.39024475529142, 53.06091397930468, 58.05683899932599, 50.893833331664794, 136 | 45.89552163136944, 50.92926806060014, 52.77715883707708, 47.547783726608834, 137 | 55.242442239210725, 42.38434385961693, 48.37086109687926, 47.71635799812081, 138 | 52.356152930651334, 41.15525621893499, 46.88239850979613, 139 | 54.590738082303844, 54.74249224262579, 50.38671962807159, 50.58581849408831, 140 | 46.848112259999546, 45.47730271012851, 51.17120968715361, 46.75688803296802, 141 | 47.51227058138412, 51.279311152304984, 50.45995377904217, 100.0, 142 | 48.56415112848793, 53.56445892576907, 49.168559049314034, 53.87408014328271, 143 | 100.0, 59.761551094479046, 49.66317120408537, 46.80211035659587, 144 | 53.55430097175285, 41.349800512266874, 47.60242023926766, 48.47738689890265, 145 | 55.70004342848097, 60.28150731706462, 47.49758026768558, 49.426377234804946, 146 | 55.902473086863786, 48.59223636518936, 46.82317911908169, 46.46198697881775, 147 | 57.65639421186251, 51.838080212884876, 53.82618009466911, 148 | 51.854635487224456, 56.20722945852302, 52.71300076634764, 44.45448445267243, 149 | 45.68934968453684, 51.64005717797442, 42.99462790991649, 52.62532793159087, 150 | 47.2010764710213, 59.53447994751954, 52.82861404332365, 46.06497981790322, 151 | 94.65298834661769, 50.44871155248013, 48.814247736137, 47.178879567234276, 152 | 53.878373722394066, 47.48408713739396, 51.98639461829668, 48.16372686465294, 153 | 52.12189583865621, 43.915133306274576, 50.13580865836813, 44.48699733783434, 154 | 47.64366544000822, 49.00756156889439, 47.81535818777669, 51.12101227321696, 155 | 43.39799983787009, 50.13559429790276, 51.944934087998426, 56.87006259033206, 156 | 53.17960514111859, 47.34433380491491, 48.90127459080945, 45.56159062582595, 157 | 45.683598452101464, 40.72944629695449, 50.84991365506324, 53.04330577744428, 158 | 48.84395890133476, 53.80920152281658, 56.99571104361269, 40.041497260658474, 159 | 48.48458407988693, 46.85995444653944, 58.25755439149063, 43.83311015570511, 160 | 61.33774604103472, 51.11776804841632, 100.0, 46.83629052695545, 161 | 59.664053833771256, 50.47458192820789, 50.44528969333817, 55.41834492432787, 162 | 45.74760748219163, 45.60651237076495, 46.441851149204695, 56.48890212940472, 163 | 100.0, 53.06069899583155, 39.59104219051617, 48.21604247891362, 164 | 43.41610624286808, 55.72161460994013, 62.32182601269524, 41.65024740372554, 165 | 39.66724746879834, 46.36547717144415, 55.33456754400999, 50.826640925805776, 166 | 41.77531561497204, 48.99809440253018, 43.32805202539894, 45.64456364577735, 167 | 49.173776778927106, 40.83384146046305, 48.50538236213622, 53.8539941936108, 168 | 51.408896720934905, 54.012635270822145, 46.23315489670009, 169 | 44.31900580984449, 50.5957336892488, 52.84732598200555, 42.629561056326395, 170 | 52.54936141330592, 57.95596735921514, 48.34267829511054, 49.017877316379035, 171 | 49.68276256083507, 51.307647986515725, 61.20256684896982, 44.93236086110383, 172 | 45.260231532720965, 53.16089871694661, 52.772215104461985, 173 | 49.63427237617948, 42.951643010659126, 61.33086845506663, 45.37175723598821, 174 | 54.270779234118564, 49.502563527337536, 49.80015715124832, 175 | 50.60595805104717, 50.26109112737867, 50.92937702792203, 52.40599788986258, 176 | 52.911091699760455, 46.76634273660537, 50.235144288375956, 177 | 52.10815518357498, 49.77048843748699, 45.916432288301195, 100.0, 178 | 99.79500753690341, 43.738559159162016, 55.30618813517761, 53.46461529880131, 179 | 48.448762411003656, 52.25926615589469, 55.65289174502125, 50.31907442718439, 180 | 46.4513462573549, 57.995927367503526, 58.16839321108371, 50.46138950620186, 181 | 49.044581511015124, 42.38079026379724, 50.36036323596431, 182 | 52.720232140607976, 45.921988704104656, 55.20938157310123, 183 | 44.60841527886088, 55.69160423633633, 43.63796747609936, 45.150586692564424, 184 | 48.613177719086856, 54.88801681642679, 48.31018470898115, 47.28479884467836, 185 | 50.49088893845113, 51.782562464378884, 45.83127610650786, 48.51060261431554, 186 | 50.60761931159096, 49.59375442287607, 42.80357958967236, 54.47486419447066, 187 | 53.13299112476328, 49.69391993788664, 51.1589695177359, 45.94390170100431, 188 | 44.97192887280337, 51.76132112945391, 53.784808996534565, 55.25999168650956, 189 | 47.52389701579226, 59.93690269202659, 47.87245893438519, 42.71481323250436, 190 | 47.913137247774046, 54.685029945381174, 52.31988576272532, 191 | 53.58466341749257, 56.08201079487867, 52.94924860872503, 41.33527139238614, 192 | 50.56803924182785, 45.72074014841646, 48.731731663759334, 40.91272290216805, 193 | 50.12661940355199, 56.894780709960166, 52.838319062400295, 194 | 49.49909783067751, 53.566862088416784, 60.119686302965775, 195 | 52.78537882994816, 53.184881466260045, 49.963927440501465, 196 | 48.16497197583063, 42.76908860913993, 58.61136926253954, 56.674432775115655, 197 | 95.37483285963339, 53.31584791806666, 46.84025140516036, 57.69749926177543, 198 | 48.19768115372794, 47.5369623784793, 51.50705903392449, 55.18708118644894, 199 | 56.44740766773484, 59.127480431924425, 50.64064812036378, 51.17434243751686, 200 | 39.81101879261119, 52.152272631555626, 45.28389033841727, 45.74838237684787, 201 | 55.92886937488515, 50.00594950453015, 53.94282669148177, 49.653839855448595, 202 | 51.76206760986623, 50.496090366746216, 54.09101201809476, 46.81828691773872, 203 | 56.892811334543104, 61.041781880946374, 49.238244029201326, 204 | 52.83053755055947, 46.213748122261975, 54.9807827800594, 53.75519647614669, 205 | 47.58820634469989, 41.423428408994, 40.47602645855985, 97.22378975294225, 206 | 53.43557502632781, 46.79755693873638, 56.97354961935277, 46.5703282331177, 207 | 48.45882741629365, 56.926463078869574, 100.0, 53.201460713040746, 208 | 43.40208302594918, 50.56608597594386, 48.94223504951802, 50.0613012043667, 209 | 49.55239225746143, 48.68157457259727, 50.22992599157774, 38.768723555391475, 210 | 54.64831931717926, 56.57692431998254, 48.29526129531114, 55.33106845954473, 211 | 53.77387139261942, 44.18367034983914, 52.94278964952478, 59.96317519341447, 212 | 49.028716303863064, 48.69484461764084, 57.01947540060502, 46.9954278295937, 213 | 57.2939374659981, 46.89072377058754, 53.44676656826708, 52.545421128284524, 214 | 39.860759927688484, 49.48995097476109, 52.48557685243956, 49.54207442447343, 215 | 100.0, 52.65388138751432, 52.00461810036233, 34.44910333531941, 100.0, 216 | 50.629799744379476, 54.54359750408028, 53.875336047859335, 217 | 46.052434663360934, 51.60242063038032, 53.795176118759215, 218 | 52.47618680912896, 56.102489909978765, 53.02418042312324, 46.66098532837104, 219 | 100.0, 44.35563510286984, 46.0238533722366, 53.81621686950181, 220 | 40.81776358136122, 47.77812872414392, 48.98868413919979, 53.020494320154185, 221 | 48.51381067872236, 49.62799699948842, 48.998473825735026, 222 | 51.606089044764225, 50.3262343768859, 55.38522751030967, 52.98853249451306, 223 | 45.32784320156895, 57.04757509009813, 44.60319828553346, 45.75764094987535, 224 | 48.02380226457509, 47.373300608837994, 55.945946214903415, 39.4732844053768, 225 | 48.313592838927164, 54.99092344281836, 39.684731795437195, 226 | 60.74601886570352, 49.50977538368272, 47.075238650011315, 49.33984920411117, 227 | 47.947293312591334, 52.81953073442493, 48.269619804648734, 228 | 51.37430386426413, 48.031416280043835, 47.70853093183111, 229 | 44.533553954078386, 46.16157218377812, 49.15569364401505, 42.20061697655224, 230 | 46.37477961450334, 50.528788794823846, 54.11933112820772, 231 | 54.118199279668616, 57.5816300864495, 44.878013918527316, 47.79929934656597, 232 | 50.89387134081242, 44.280601425024756, 48.422768887144066, 100.0, 233 | 89.8783547666414, 45.38093992415343, 53.833939756342055, 42.15434312108208, 234 | 44.958789883706146, 43.610351490811865, 50.539930846036206, 235 | 42.496470902701574, 46.5733411864179, 49.320706446462005, 49.43305049030741, 236 | 49.99277126457647, 56.049362125730084, 46.27068754442274, 53.47725454533863, 237 | 45.287602442194206, 100.0, 57.556761545359606, 49.32555300923058, 238 | 44.345540143247746, 45.91059438177099, 86.32336280089763, 239 | 35.553423963694456, 52.66510413975228, 47.02875608735882, 51.85408117042555, 240 | 44.45819704413359, 51.59371208766354, 52.16597725419578, 46.9287219457128, 241 | 58.99341640623637, 48.82890566568115, 57.316850518245545, 46.78204947034521, 242 | 51.00613106514145, 54.377945510055014, 48.28226444376442, 51.0316596601845, 243 | 46.56209751923318, 53.09465812616785, 52.820627885857995, 50.353722935193, 244 | 94.62835838808063, 46.16121435201082, 48.197711020657444, 46.01731547468192, 245 | 52.46420338778156, 51.14750060472596, 62.82252113757258, 57.80671548474383, 246 | 56.40575275219235, 47.93504647601462, 52.52250266738343, 49.342612158543304, 247 | 51.796439801900654, 58.35614732533284, 49.630685738949715, 248 | 52.42202637491163, 53.96265279127665, 41.92316559884853, 49.57768654119325, 249 | 46.9774017477213, 47.12951667845173, 55.97819365739737, 49.551592852768316, 250 | 100.0, 47.750297458561484, 48.591476495696256, 46.62315525459026, 251 | 63.14827389404478, 53.71660441669297, 50.31033155200592, 54.08349239044457, 252 | 54.8421932004225, 100.0, 59.70473079216532, 60.66383379455489, 253 | 49.33512821891453, 47.902451401424, 99.11026517957463, 45.83177680909442, 254 | 54.097165041100894, 53.34487472087835, 46.7775740145402, 53.69315144061702, 255 | 50.088287527339524, 50.11404504068261, 99.6759478102064, 52.31709267908309, 256 | 58.61528451339485, 55.603883259592806, 52.19324752187055, 51.04813111333842, 257 | 52.53012957627258, 52.228380187246884, 48.663198165122935, 258 | 53.73440942526312, 51.19234150926244, 36.76418078551721, 50.23983757665478, 259 | 53.367602433271784, 43.50255487859434, 46.73751651930589, 53.45621916862426, 260 | 59.15066050066285, 48.35955540190714, 54.83427431350944, 57.77300025467747, 261 | 47.08987000345057, 52.25122307524311, 45.6241146046099, 45.71400780607684, 262 | 49.04280172452547, 50.9071700628819, 55.866614220236514, 52.443878061639595, 263 | 49.96384668233349, 52.11642858845329, 49.20087656135781, 51.268589191359524, 264 | 44.281645866913905, 100.0, 49.02268916695213, 98.67536768346022, 265 | 45.713267126654635, 62.181969809880755, 44.829973084509724, 266 | 54.031795152851345, 100.0, 40.76334090286456, 49.492954180158044, 267 | 55.37189593903331, 54.57090734915354, 45.644839287580005, 46.09957844258216, 268 | 60.825116935125045, 49.08437279811487, 51.074071515175234, 269 | 53.45361494305088, 41.33694428162846 270 | ], 271 | "ram_usage": [ 272 | 29.17066215316325, 35.664334595939735, 41.211705192544905, 273 | 32.297490819757705, 32.984434866400996, 46.37661776264772, 274 | 38.37016505644196, 35.31957735462749, 40.24334116919243, 41.08708726124624, 275 | 38.22162704580998, 39.10912703127555, 42.120596612743476, 45.71378718916715, 276 | 39.34212822719375, 43.48193142615003, 32.61810091040152, 40.83092299303964, 277 | 51.21770440272141, 41.38857682002753, 43.404085388377176, 38.72823639419874, 278 | 33.71890263154329, 46.51389158313707, 41.210389480995964, 279 | 39.480122559305336, 41.47154551924578, 47.09755030547171, 35.16191887698817, 280 | 38.002004554741085, 40.63560724778635, 40.27874449042712, 281 | 39.908607995881695, 39.01591749135447, 32.203450982546556, 282 | 38.45264792039079, 49.14481089976049, 45.63245226745937, 36.01306248605159, 283 | 43.10029683383575, 37.454106134058215, 37.4235273949987, 35.96491814299153, 284 | 41.05207560326244, 45.436605288380754, 41.688131600519945, 39.8110881130413, 285 | 51.77099658199108, 34.09321036353349, 42.946775202252155, 51.49945601602028, 286 | 43.64480731163851, 34.14043518461566, 49.649336902903435, 39.86577440963323, 287 | 36.474665055772185, 43.07163888125361, 44.39650565028145, 37.43863489813372, 288 | 45.54076591309814, 41.02932780206887, 42.00010239884062, 49.91443194397741, 289 | 37.17277788240048, 48.81698477706642, 35.80685523112877, 52.52152945157871, 290 | 48.284509092520025, 39.372001287837094, 35.557818344535136, 291 | 34.70766381706769, 47.79498988275043, 40.956864382471124, 51.27192562100771, 292 | 34.78182154030851, 40.126412000734554, 42.81294651049448, 38.00158884642794, 293 | 43.005295980319055, 38.39290295050266, 41.70465790270873, 294 | 39.251684225400716, 39.35742311471582, 45.4518591253845, 45.78985372511187, 295 | 38.17268736782141, 42.807148781452845, 48.596445502873664, 296 | 39.17267687000766, 45.531184090222744, 49.20655184150285, 50.11651215028771, 297 | 47.89326718843471, 47.27570206962537, 49.10193595915925, 39.24971760394863, 298 | 52.234050881360005, 42.90379379079893, 47.656120379328385, 299 | 50.418215864890236, 40.87541437066514, 42.24876921325416, 300 | 38.369257884336875, 44.1744217735121, 52.345611485702285, 57.00910564511843, 301 | 35.893178431825135, 47.80397347439905, 49.87556335734095, 38.94681990952931, 302 | 35.929042244899385, 48.75582127900333, 49.17006745209038, 36.54157293912158, 303 | 38.65533846127973, 44.63739846468074, 40.44716484415402, 60.10857263119995, 304 | 43.552889483160286, 52.342911778886716, 46.086093049931506, 305 | 44.637370365454494, 46.83981400262012, 50.15233268216668, 51.73568401921635, 306 | 49.871566587540784, 48.454425317373065, 41.36542548149647, 307 | 50.205499102189016, 41.02791650751778, 47.63753827703706, 308 | 48.407491435653256, 42.44600031774172, 38.01072585108828, 35.57167850568254, 309 | 46.197437090100735, 43.145514022744614, 45.06181321117842, 310 | 45.160028686115545, 56.38848781451611, 44.374814012210024, 311 | 48.97892487237735, 34.21114228270253, 44.227490763688245, 46.10484988120284, 312 | 49.47336628683868, 42.758840046118344, 45.109020899588536, 313 | 50.167475306179696, 36.96850629360085, 54.76481796999589, 44.4747911265955, 314 | 48.53215908079185, 56.617415395823826, 49.76634327046334, 56.28790400217851, 315 | 51.467946444591306, 43.08011263250004, 43.8794201436281, 61.136114176788624, 316 | 47.06295408162404, 45.46034331265922, 45.23781511547055, 46.96958936597263, 317 | 49.06136974433752, 49.52489313519185, 42.28060589596713, 46.906014226482384, 318 | 48.101735967132036, 50.66762407904215, 44.29226435586959, 51.74848174789114, 319 | 41.31335177939638, 41.68639029810889, 45.09387240426969, 52.54354638942835, 320 | 46.67262454554882, 42.01252824557754, 48.232933945056935, 56.54082135898464, 321 | 51.532699999370664, 52.20315920918361, 57.47003786565018, 322 | 47.736760064929676, 61.0923202599586, 50.47181122291343, 47.28689866658678, 323 | 52.15723476105684, 49.82998649559094, 40.69407626397191, 52.61387636167758, 324 | 49.028161075309036, 44.27404698508338, 49.18492799581083, 47.1290978808268, 325 | 44.36643904676891, 39.213550090284755, 44.1460616683938, 47.94539445841164, 326 | 47.91813915890219, 47.29668641518636, 49.5272086494526, 43.10596080252371, 327 | 44.042268751479504, 51.60529581873003, 56.694972176024564, 328 | 52.630855650478594, 45.89535276819162, 53.557868305452956, 329 | 51.33628326613485, 46.98607115488551, 58.710407132605226, 44.17540884176535, 330 | 40.174164282625384, 54.081333545288444, 45.270818824173986, 331 | 51.38174106250089, 43.87046979456741, 42.473509578327395, 47.79123895402163, 332 | 53.151912754485735, 47.664457453524754, 57.7755720824444, 52.14983915044613, 333 | 57.50060859463479, 38.385846076227224, 47.763688165737584, 334 | 53.97336801405754, 43.886937012264056, 53.50126494412668, 335 | 51.449973366628846, 51.68956905121356, 55.72004314782074, 49.4159038434053, 336 | 48.14721181807222, 45.853977629723005, 41.88578620186168, 49.61676270135408, 337 | 50.9641442457181, 52.58976284852259, 57.47395184084232, 56.556050526393484, 338 | 43.55205219579923, 46.94122707447928, 46.60768640010558, 41.71015260418099, 339 | 48.60610343757283, 57.16811850357544, 51.81881055671967, 48.232589375543014, 340 | 43.021569172592336, 52.59861689129651, 52.21281413955884, 52.4517051083927, 341 | 50.28677939747997, 53.855695556805664, 53.88703450988811, 50.7044686017001, 342 | 53.878349516795645, 51.32015028333433, 57.42844991021019, 343 | 50.077909693132895, 53.308234602088646, 53.08777061145227, 344 | 51.647479006976276, 43.69984642152933, 54.4369888800732, 51.05220717971359, 345 | 56.68879230951461, 48.94374741706773, 45.13146769950993, 58.96492994941603, 346 | 48.67359215689716, 58.09755944473545, 49.290781920675684, 50.19835570931696, 347 | 51.89499608955469, 53.275504897167465, 54.29921770184138, 48.04603855295714, 348 | 46.51526175652304, 50.52552848981415, 56.75575007129171, 45.65764674905947, 349 | 66.76328143334729, 47.44555980577657, 40.94957451736287, 57.52905352236612, 350 | 58.151231367173224, 39.62961055735486, 54.84125554255828, 46.39829064010422, 351 | 50.817731772976515, 43.905540225223376, 45.78085690559671, 352 | 45.18408269150464, 49.742343254134056, 41.85702967546804, 50.8557296279569, 353 | 46.29325051528184, 52.80443839265139, 52.109397342535644, 57.03802512876923, 354 | 54.49833557840698, 47.210596673458426, 56.66340161762186, 57.04119926507761, 355 | 52.17540600162349, 60.143350251984785, 48.24710506311286, 48.10557021214241, 356 | 48.65569942070742, 54.28232729230346, 55.11360691091993, 55.911076461547836, 357 | 54.59001935418815, 48.700998927769255, 54.964047595967166, 358 | 49.44418691622035, 51.277363800404025, 55.603675712225986, 359 | 53.653125432893766, 50.38616491655043, 55.92866431935133, 360 | 48.950425645299696, 57.08017613028842, 42.40700942621088, 52.18762322648191, 361 | 59.98406668442956, 44.70047564563501, 58.16816753504523, 51.90161088490446, 362 | 54.69114132305763, 41.53562237866715, 47.00392554388384, 52.13713311175357, 363 | 53.32598327208296, 45.48329052405327, 45.84136335295394, 54.673592479262695, 364 | 56.65480538060804, 58.065073870700736, 54.01367374419914, 365 | 55.747173792017314, 51.81923183249817, 58.64334566763331, 47.49405687538528, 366 | 54.872375846969405, 61.333561853312084, 63.34653735848471, 367 | 54.14056259863385, 59.79155359282673, 53.95045615145556, 56.90698543067865, 368 | 62.21986757474709, 57.19609694176496, 56.87982497743231, 49.45128389995191, 369 | 51.17001074276585, 56.022094266975515, 52.103489410223936, 370 | 54.587441509827535, 52.15807083783763, 49.11359113056275, 56.51485404809514, 371 | 60.32809370367782, 59.2248105481647, 56.861660550840035, 50.45101820874735, 372 | 59.1689157133926, 55.473009160762075, 49.46669298742692, 59.36636436120493, 373 | 49.39289817842165, 53.443372696420155, 53.74844445118735, 52.3627997600301, 374 | 54.26899295029983, 47.029094433005284, 59.152476997963724, 375 | 51.606267949961655, 57.5122961752332, 62.005325425655045, 50.03757702071676, 376 | 56.933343651170624, 53.053658849923586, 42.92898872552425, 377 | 53.98703360982961, 52.263088117436375, 57.55189745890428, 55.80050980928216, 378 | 58.416298367127496, 54.8838079292484, 61.58407899453806, 59.34431235064578, 379 | 53.06301976828566, 56.08009209767272, 61.949531040303896, 57.20236801479574, 380 | 49.42772084677233, 50.847971338345154, 56.113933386547984, 381 | 57.33196549491054, 52.12407630150534, 57.60802373377195, 52.69307245799544, 382 | 54.29727000168264, 54.99424449267795, 58.44183177985453, 47.64610115182424, 383 | 61.45438441004008, 63.14663224698664, 55.15650201709501, 58.8371451416491, 384 | 50.72500184824782, 68.03255489108182, 58.22492608023042, 60.46925397347156, 385 | 63.02325625235523, 63.84823887384511, 51.87230084765068, 62.62589036791155, 386 | 43.38370002831705, 64.33109734479847, 51.73090583179592, 62.7046856207576, 387 | 58.841295137500204, 54.58842979605764, 55.93307788336907, 388 | 53.510249143982556, 66.93230664102745, 59.31175947612343, 389 | 63.268854796892114, 52.17433482042345, 60.602291710939795, 390 | 59.11584775938211, 56.28182250366347, 62.73951254319308, 58.42373448706023, 391 | 65.3352837921817, 54.77012755639046, 58.337942626964576, 54.16006222343655, 392 | 56.39963949387644, 59.876468809973616, 57.709650019346796, 393 | 61.86595977000098, 58.40640815172319, 59.65732115089416, 55.96370182278294, 394 | 59.12004989566433, 63.89369111602454, 64.81527403155155, 58.295637193283284, 395 | 67.33332351576736, 55.14636994583257, 52.32994176575858, 54.899882362574836, 396 | 53.31622393986962, 58.02867285369321, 58.13688447236852, 66.6402418646808, 397 | 60.23898035863539, 50.877123433012244, 59.117993618953626, 398 | 57.70870703621586, 54.18831886461629, 48.08663190967725, 59.46188707699963, 399 | 60.42212719992206, 51.52822366335615, 54.02272562887203, 63.10887638090943, 400 | 65.1584143720281, 57.4813499112416, 55.33264890336548, 61.86955608005377, 401 | 63.90163278603659, 55.56882693100886, 54.55936122345235, 66.83463182771897, 402 | 57.720560277691284, 61.468325382449684, 54.12146994401658, 403 | 45.14932777644133, 53.502363644749366, 62.540961713516616, 404 | 63.416601607951506, 69.01235619498739, 63.4113513435875, 58.91278138432287, 405 | 61.622536308455246, 64.44563732063074, 52.58039655596157, 406 | 61.299875322396964, 55.80948758117838, 68.2254032032957, 59.501892704636454, 407 | 54.012605792774764, 59.39511712300481, 65.83635031384499, 54.99208583840329, 408 | 60.27893367750274, 54.41209123540251, 59.78746019047073, 58.94631215795301, 409 | 63.831295541648586, 62.2153927334048, 50.103603754550974, 64.76376653383961, 410 | 53.498068138095164, 54.5307094819464, 60.67216776306204, 61.12224148266223, 411 | 59.83939999389405, 61.93922542937247, 65.64397789003993, 59.88405828274276, 412 | 54.66963556697198, 55.970599852575866, 55.47990143114549, 72.11601090374813, 413 | 50.523300595768816, 67.7826926651919, 62.86541834546077, 72.33089513712417, 414 | 62.58099886633678, 61.16754565640465, 67.02849411796704, 63.98996548134106, 415 | 57.281042606238515, 67.43810796866126, 66.77834079061006, 416 | 58.860893759805826, 64.7697729750038, 60.72143282722291, 55.65651841111673, 417 | 64.58541821748419, 61.112787613092316, 64.44208582747541, 61.4731958415321, 418 | 69.71864012724335, 60.77522964325932, 61.123707158260196, 419 | 62.348210556070335, 65.63765304717974, 66.23981196511008, 59.89282998840755, 420 | 62.64412379726628, 65.41533841646022, 69.55914308130805, 61.367934766021534, 421 | 62.995848691582665, 73.21445288689155, 60.43630688155292, 61.52767284669656, 422 | 67.672749187491, 60.456581920837024, 56.09307886276283, 58.97353269714184, 423 | 67.336834073032, 62.256206972211274, 65.14071643624742, 59.29393888674644, 424 | 59.11366740505427, 63.20022024666048, 62.42121853004607, 58.298212655253856, 425 | 57.0764736947215, 62.43685433174969, 69.86473592251926, 65.16568139842518, 426 | 65.54417328003622, 60.715002479545525, 62.72462074005709, 55.79641467034952, 427 | 61.162292944549705, 67.3840045726754, 62.51871671750552, 62.04646463604442, 428 | 67.90261905501302, 66.48504072828034, 65.2171942033219, 70.2517269377898, 429 | 65.63396249923335, 71.36080318147414, 62.62741588822433, 65.12675927728404, 430 | 69.26028125140836, 64.51268623467135, 68.49894181502285, 70.75565412157394, 431 | 58.953557258422734, 70.13749185869877, 70.37674041321262, 65.94458743701028, 432 | 65.11545863235492, 71.75257553093695, 61.084749781493876, 70.56388060357588, 433 | 69.43757905547008, 65.04564813012975, 67.82483992209842, 60.872237536817956, 434 | 64.97652915836876, 64.83707237829266, 67.8311957430362, 65.11704639856087, 435 | 60.04903811598795, 70.37962754840474, 57.486016549079395, 436 | 54.791197136866906, 67.8023715691481, 68.68503870044528, 67.99918249939424, 437 | 66.6274911206079, 67.04080353886297, 60.488111353971895, 57.58655449561516, 438 | 70.03726244881133, 68.29816783672631, 72.42387964303332, 58.03382642686159, 439 | 62.66415494796156, 68.5105384717027, 68.87113933570814, 68.86070690101157, 440 | 67.92631753027324, 69.03735245426564, 59.974765483838986, 51.81509404249665, 441 | 67.03732070718186, 53.54901944562587, 68.77222100741183, 71.06876913113068, 442 | 69.77634723387516, 68.58042538019477, 65.73896774652506, 75.5075198864863, 443 | 62.66870682050239, 69.69968785308623, 71.18429413329096, 73.41110610967338, 444 | 60.497660340893155, 62.764431660599946, 68.292933977346, 65.86817858429073, 445 | 71.9865406793832, 63.55641368842976, 68.87703218799514, 68.69440703266866, 446 | 62.47516531625867, 66.98919211946853, 62.73477203386425, 69.57024743863893, 447 | 68.87511078536913, 61.02643956142071, 69.04504028169701, 68.3806996407238, 448 | 62.531196932979526, 68.37261929393114, 72.86752277433132, 63.72105228291477, 449 | 57.998676023222295, 61.57403546806687, 64.74703534107832, 450 | 57.272228668040896, 64.37171302403058, 67.72205569609237, 451 | 58.290031795236985, 67.23162585462443, 62.556916814670444, 452 | 66.96921107381029, 62.26117455380611, 57.93248314437156, 71.20731892685642, 453 | 61.22346291323797, 68.4888233374975, 68.09436121364978, 62.553106828605436, 454 | 70.13860261750526, 64.44004330792707, 67.5935512572627, 70.41411767078827, 455 | 68.70676488233872, 79.43867697804666, 69.8338188754132, 67.80357525355645, 456 | 64.57097795240382, 67.61655887901352, 70.15786074697539, 66.8364009608408, 457 | 61.19543803714163, 74.14073066670444, 72.11104915286913, 76.53813220006002, 458 | 67.67054711936736, 70.92713906411173, 69.37667742960487, 68.41100394481735, 459 | 65.65576249974362, 71.5859880883871, 76.76237986131353, 65.97208547898595, 460 | 66.96208197193188, 63.79655534342015, 63.52137724735455, 67.16469354764297, 461 | 73.83088515857101, 65.53259700530508, 70.43411367559378, 70.9836128295877, 462 | 70.07631455186616, 71.19215236707578, 64.28942358090404, 71.22898942578416, 463 | 70.1120215558407, 69.99347138014679, 71.08389063343145, 65.99833559968603, 464 | 65.47532687784098, 73.07935451642697, 73.76417166739944, 66.67470745767638, 465 | 64.41974433039468, 64.26031942144306, 74.73049654565743, 76.10698074344577, 466 | 76.00773523964536, 75.20707427069789, 70.43327507437881, 69.41798485528857, 467 | 72.45299579414387, 68.52543962770378, 61.871677658996134, 64.15878096561192, 468 | 71.14917201237188, 71.48346121055519, 70.40630661870739, 61.24681376759599, 469 | 70.33048116794544, 67.68004927962075, 68.58170591953082, 63.96294400078551, 470 | 64.13809735666226, 74.84798756814767, 68.84561479737154, 69.976065372598, 471 | 75.89148695039621, 69.96589937267156, 63.93470697913464, 63.86188973675862, 472 | 65.41527772894833, 66.2480921075089, 64.89436765195688, 70.80200108364716, 473 | 61.834007894645815, 77.77233737935461, 71.03605335428176, 74.99412958179022, 474 | 71.52710335678627, 74.96310574823084, 70.4726503137301, 67.34074551385147, 475 | 75.77828604570604, 77.22508021293689, 70.17536728538474, 82.47121846785099, 476 | 68.28417593217232, 65.8565720739199, 81.00670549989687, 79.27266549506541, 477 | 66.35741625599302, 74.56792823607847, 70.99007366566966, 76.5095592777061, 478 | 64.68910315103122, 75.25186540668189, 75.20675173000183, 63.10988444746302, 479 | 69.3088426910574, 80.95970608305188, 63.96620924385618, 69.97688128034997, 480 | 71.02968915280096, 61.466442980278984, 70.3424172206251, 69.1222898404788, 481 | 77.78447346575786, 71.37507708385986, 78.25539795324653, 78.32355199678874, 482 | 68.19523004726136, 69.8089416305248, 64.38067877998205, 74.68265334784832, 483 | 67.2857208442717, 66.55959021342181, 67.06900540873805, 75.91131558946836, 484 | 67.89656440413283, 70.70858583594917, 70.5372743781255, 70.5178542544211, 485 | 67.48975562661263, 63.37212039068956, 74.3575305720222, 61.9250818172256, 486 | 75.18793455315931, 66.16063962283897, 67.8881003452625, 66.87441252154564, 487 | 67.27753937614459, 76.74333031572327, 79.70979333463072, 70.40240275802218, 488 | 67.91476530279424, 67.62557274838618, 70.70331735183905, 74.30686399916159, 489 | 78.50428747875743, 64.9774048773993, 64.6901921162686, 73.79834876297973, 490 | 68.67449283725436, 70.82805679965175, 76.4769587701738, 78.6962055158346, 491 | 77.09879253054147, 76.68122273460482, 75.85075290722907, 83.98008956341089, 492 | 67.91592500178875, 75.622397340405, 80.62042407601278, 74.8297268597945, 493 | 69.8608481481402, 68.84625496756907, 76.89799456344444, 75.40129786948233, 494 | 69.96608341074592, 71.84226127547147, 76.85138267302692, 72.3963825640228, 495 | 71.49051070778404, 76.49961663874839, 75.11476997389504, 83.61827499750004, 496 | 75.05745641851664, 76.22391282405975, 73.63956270665203, 72.74200758417247, 497 | 71.02536842527331, 72.8801179805079, 77.73594218500219, 64.57369292273226, 498 | 72.74117478637432, 74.33809810640753, 64.93407379957696, 76.47680594868008, 499 | 70.51189805801151, 77.52604160125374, 74.21945077766122, 70.46103099271556, 500 | 70.07192636076296, 72.10323874714521, 77.20932523138501, 68.52559376431314, 501 | 71.57588661760367, 70.70485442017088, 84.66021708462796, 74.60061482553061, 502 | 76.93782192539877, 71.48905334601409, 73.5410112013203, 82.03667984253289, 503 | 70.06771711012628, 77.37598384012236, 72.13236338544722, 72.65194813738077, 504 | 80.95533807255349, 70.13640014559596, 76.01835477685762, 75.63287646824226, 505 | 81.15708943590383, 77.75054143485087, 73.8893194280658, 83.58108072432871, 506 | 73.16793655869218, 68.81307361693592, 86.14521099602416, 85.62606687134219, 507 | 73.59911130653751, 80.47054690872122, 77.94812118473115, 75.01051932360522, 508 | 77.70109992255026, 79.93399574586772, 77.77652593247353, 80.59361852173771, 509 | 75.45931080158982, 84.52323740398776, 82.146316878556, 80.62310575407432, 510 | 79.69516153492899, 80.62096206521618, 76.63358711098564, 71.37118631830472, 511 | 71.21007521765901, 74.58196159563552, 76.09584189004993, 65.87375642547, 512 | 73.49631366988247, 70.8670989915467, 78.53564541051789, 75.55134148215221, 513 | 76.27288185780077, 77.2961951103013, 76.12577723319795, 80.79215342087079, 514 | 77.9331106019169, 74.83089293903245, 74.77759607198374, 77.05990094652675, 515 | 79.60456235542966, 75.66855018060852, 72.76006355918453, 69.69690460977559, 516 | 75.1229649744433, 71.67151163924098, 74.11218568671644, 80.95822268246886, 517 | 74.20483107082869, 89.08680841586204, 77.13303331483189, 77.22021735507582, 518 | 81.71118595574151, 72.45965782005439, 81.63948893336737, 74.51840729700453, 519 | 68.97871258387788, 75.42664370195753, 78.14130015306799, 92.60191056905725, 520 | 74.49199179996762, 75.35349549090033, 72.61206671152738, 74.34148965306764, 521 | 66.21438823524488, 79.29179092210188, 81.16985317464808, 81.353264904229, 522 | 67.36287449891805, 76.13470306918622, 72.15742304098853, 82.84887027608718, 523 | 70.20589206560263, 82.94177249654462, 73.42726418643657, 74.95903668015559, 524 | 78.94074207199652, 79.87147885667528, 76.51234841093745, 79.52853368862847, 525 | 81.81717325681586, 73.7091479381371, 77.2201681236893, 80.51530512054721, 526 | 84.61946322160085, 77.88142384196104, 84.14476501244556, 81.88292074619281, 527 | 82.2910681781131, 74.85039407569133, 77.73043551977884, 85.69937511292983, 528 | 84.631793900004, 79.16599769389026, 79.46714275913368, 70.62334834341596, 529 | 79.77399900924871, 83.16825237181367, 78.8746863601574, 82.73189842369607, 530 | 80.63702254790797, 84.70381100079709, 87.06591092686506, 86.04042137492263, 531 | 71.06233572505526, 77.41761858570247, 73.17646803688324, 83.51422789503474, 532 | 77.92338401556519, 78.5102351865328, 81.00724994397447, 80.08750583101536, 533 | 77.8540438457582, 78.14616142136843, 81.6974133116716, 82.96103408369622 534 | ], 535 | "network_availability": [ 536 | 97.14843681928949, 97.603205699706, 86.67620229493504, 88.86805542351132, 537 | 98.23549635234296, 93.02158833412604, 100.0, 93.54119702181396, 538 | 93.54287828353557, 98.71122235129071, 93.16966270810971, 17.55827706439539, 539 | 92.18056795060463, 89.31984778743032, 99.06499171217664, 100.0, 540 | 96.10448788398594, 94.16492845681434, 96.22149620614961, 97.75490414426082, 541 | 96.7679773175026, 95.2940344610515, 92.0840973387381, 95.92268940822774, 542 | 98.50643211048191, 92.3448523232147, 94.81140012564386, 97.94467510946139, 543 | 100.0, 93.49738454228093, 97.88666717098668, 96.9976113195368, 544 | 95.7369133600137, 94.22639325078978, 97.85555741418378, 91.77593559368495, 545 | 49.54384922359634, 94.59527823520806, 94.13193309465264, 89.24723037543046, 546 | 94.07849792174235, 36.85458556186194, 96.87330973884774, 93.06209265888903, 547 | 93.48232441899243, 93.86537111134997, 95.82030698741488, 93.86475020787314, 548 | 90.28636460661818, 97.15379843451974, 93.66803689090037, 96.33503266814837, 549 | 92.27836835884706, 95.0202125235567, 12.896397409000176, 96.00959883725824, 550 | 93.71516487757344, 93.68163095803204, 96.75820877523674, 89.40215750197886, 551 | 94.9385090357859, 95.56556322555902, 93.85420918014064, 92.96463771925211, 552 | 96.91820497725381, 96.75548727441915, 94.93284240323207, 91.48921853983217, 553 | 91.47009800134586, 90.57113514032066, 90.41478524090608, 98.80656950674819, 554 | 34.39680059927816, 93.88689983564932, 95.0452470325647, 93.73804419373943, 555 | 93.15212201442993, 95.5990191982466, 94.954155886247, 92.56376523379244, 556 | 96.9376591592566, 91.06154261458484, 93.80139268439657, 93.45125352039356, 557 | 95.89123253849412, 91.48192113411484, 90.98381805981698, 89.26983273582933, 558 | 93.17900948436086, 98.85640264751586, 94.62061044291733, 89.32874951326976, 559 | 100.0, 96.22666271776033, 94.5217532034817, 99.94146280912709, 560 | 94.47610044266399, 89.56580106640334, 100.0, 93.71894874965488, 561 | 88.80965833851293, 94.06280626306167, 8.565079107464863, 99.61469284780296, 562 | 95.49910415988157, 92.0592828004574, 87.10995706965126, 94.1259933315522, 563 | 96.32035331687787, 96.7947155538935, 92.94566161885169, 97.93688686396575, 564 | 99.171255184421, 94.94864930502422, 42.82253568005269, 92.01380331546177, 565 | 92.19115822433892, 94.88452057757165, 86.61882338408377, 91.58939715004078, 566 | 92.68288150425234, 92.83575861780632, 99.24901591258931, 96.02564277973025, 567 | 99.43759428831822, 92.966148989159, 93.79472819901379, 89.34755597090258, 568 | 94.98450613885734, 96.04858425114804, 88.09327580620749, 94.86184723352633, 569 | 97.32885392534496, 94.74122632197518, 92.83915658713329, 96.14765147335142, 570 | 37.85129791780513, 45.58678582438475, 16.056274545823708, 97.60858680965552, 571 | 96.33770205446777, 100.0, 94.09770399865653, 90.9450130985793, 572 | 96.75471330807059, 96.22868378087948, 100.0, 96.27846576186691, 100.0, 573 | 93.04391721349829, 96.39866466695415, 96.51652207799712, 96.5707957394379, 574 | 91.8115704988312, 93.93730318771283, 15.66313122561176, 97.3584599747641, 575 | 95.60599257280849, 90.71030042966035, 96.09571723487512, 95.28240012354625, 576 | 97.35434374929254, 96.22666613668893, 92.25292407097699, 95.37342359499063, 577 | 16.665318373930617, 96.58045573737782, 98.1471629734902, 95.42157076945178, 578 | 96.42747174599685, 89.94164265607078, 92.11808312408867, 93.25283809411074, 579 | 99.0847290643455, 96.2931796515708, 99.73901431351118, 98.7446998697853, 580 | 95.14703539522264, 97.07083192350449, 94.31087997571929, 97.39833989705492, 581 | 90.62700852473233, 96.6796548393201, 96.47214596485824, 92.00699947840512, 582 | 96.61271518215429, 94.34792452199181, 94.5975427934661, 97.43467755031085, 583 | 92.58047969379723, 36.46956384921572, 100.0, 91.03266782398418, 584 | 96.00873880144812, 95.0443921849098, 93.88457436298033, 99.1820847232967, 585 | 16.031363690224552, 93.42231561329898, 91.07951681854128, 93.12315086293307, 586 | 89.90419779698068, 95.6098995166076, 95.24368190921182, 98.13798974169083, 587 | 97.89163549396119, 94.61947518501931, 93.93857076718268, 93.89503816691848, 588 | 98.5294712871988, 95.55907501090928, 90.58161847885792, 95.86675850326694, 589 | 89.57889170818278, 13.44154926566209, 88.24285607264063, 95.86833071950456, 590 | 96.48871006252135, 92.42131607156868, 92.3364571227169, 95.82106776130087, 591 | 97.33512518670607, 90.70391531931735, 95.09725746150993, 99.59699357706315, 592 | 97.4391821376379, 90.85372612693084, 91.9391269900972, 95.99138077590786, 593 | 93.21779906004079, 91.70476287583435, 100.0, 91.87723430362952, 594 | 94.14135563334156, 97.57493028730191, 97.25779954824678, 98.49241093578512, 595 | 94.60739945416795, 85.46204065451877, 93.80680064781885, 94.1289160703848, 596 | 94.11694050765139, 95.605472263028, 26.8605293781992, 95.74897192174897, 597 | 90.20749420126975, 100.0, 95.95772253249402, 97.25761451777225, 598 | 94.276625057088, 96.99324614764713, 100.0, 91.67651897236759, 599 | 95.3520121910497, 99.96026566009299, 94.11557650156776, 92.24721602850795, 600 | 94.56226191688893, 94.6714976935531, 97.43075155968947, 94.2447774891125, 601 | 91.81212841901187, 93.49993579974728, 10.23424048554204, 91.1284801282056, 602 | 92.63594138925326, 96.72700674579971, 90.34651995176596, 96.25649948102202, 603 | 98.82670654344695, 92.31621190272939, 89.67855468805647, 91.5152995318812, 604 | 94.85758793144673, 97.1757719549649, 97.10159432706344, 97.8046370798495, 605 | 93.76798819800189, 94.89969292952743, 95.88208315679704, 91.2000885733888, 606 | 96.22895233797774, 97.6692278442267, 95.17915620969214, 92.8782769061894, 607 | 95.46775006541672, 94.29600684492652, 95.78789153090626, 94.5276638572937, 608 | 96.98032153124922, 93.0982574630115, 95.54789478936846, 97.90867507564782, 609 | 93.27940580808786, 95.32047015059858, 90.26132100349972, 94.0939123559954, 610 | 100.0, 100.0, 92.77693167949727, 98.84516941851892, 97.81251633431734, 611 | 98.13825782204881, 93.38625908229407, 91.40664389513363, 95.8695734607528, 612 | 86.78601962163255, 93.42966070643632, 98.63150512726344, 98.98452045743534, 613 | 91.28482418893253, 92.91079953031779, 15.777305975216466, 92.1643876474067, 614 | 97.09441089730093, 96.38969642168368, 90.47414474736013, 100.0, 615 | 90.68917197807816, 93.44559536389485, 99.81191978147658, 90.02147332053221, 616 | 96.42264446346718, 97.76599971214557, 94.12038246183576, 90.11206914816964, 617 | 99.96163945722216, 99.53771866245413, 92.23868529859864, 92.88481440795124, 618 | 91.79077433667624, 91.10516700183408, 95.756907349643, 94.10555859071826, 619 | 95.66365461626155, 95.1310651014234, 90.20623582658415, 95.52579806590334, 620 | 93.38910949267809, 94.60364026242998, 94.53687798669662, 25.982713584312144, 621 | 96.41619411869196, 92.37749167907471, 91.8370294610757, 100.0, 622 | 95.25093180515356, 92.57951612030188, 48.88787486804358, 94.6525049808177, 623 | 95.16766383263628, 93.69221619854113, 92.12812962222712, 89.30776202297947, 624 | 94.60627451369828, 94.97067888122905, 90.67546956332673, 91.68006730697047, 625 | 95.55759503444428, 97.64677444238428, 93.97710887289506, 95.47724727708903, 626 | 98.39640911868158, 97.24919600742062, 92.72559801001292, 96.51644250639139, 627 | 97.82231694529007, 94.86453766246278, 100.0, 98.03308667782908, 628 | 95.53270723436023, 91.32562806455763, 92.50792948620419, 92.10567614368821, 629 | 97.22093985067697, 97.90803092350954, 36.72499886521823, 93.36654458452237, 630 | 94.05164472224439, 88.36254953676833, 93.46387457697269, 96.24439752870533, 631 | 94.26454988761519, 92.91081911032305, 91.07056568581055, 94.72276038413756, 632 | 96.14008431762281, 94.57463615879928, 94.81832504529042, 93.22467513895369, 633 | 95.64696972041158, 92.70680989489834, 98.13967339269013, 22.142176258981085, 634 | 99.28290475934826, 91.99165141543256, 94.70386807946515, 7.760297566417113, 635 | 94.6150387167731, 28.130164192917707, 96.1752224762006, 91.21096398852575, 636 | 98.19975416902861, 97.3210942078135, 100.0, 96.10641785103952, 637 | 94.51650511153146, 93.84695681974654, 99.07415192555268, 96.4201550007568, 638 | 96.88532078809696, 91.42696497996224, 93.28898448000743, 99.0894953028345, 639 | 95.09670713212596, 96.55015451396939, 94.29281324587471, 93.63615759001861, 640 | 92.82129194772118, 95.55429849474093, 91.7307887044836, 95.10858989543591, 641 | 93.3586751696412, 100.0, 100.0, 94.7060662120583, 100.0, 100.0, 100.0, 642 | 95.87878032347744, 88.7795844804943, 93.50989791128944, 95.86665576315187, 643 | 34.820780297206724, 96.6272983078213, 96.53452649598391, 87.92965221192208, 644 | 93.27196114318708, 93.03181703765617, 98.23739422388881, 93.58567030660973, 645 | 96.23406909528347, 93.27089365929196, 95.85339561162708, 97.11042877929745, 646 | 95.25505567120086, 98.09340345289924, 96.52654866585442, 94.05969628933404, 647 | 90.83705283985017, 87.00796846542748, 97.3199458228346, 23.072079004022054, 648 | 96.35195494516009, 96.03597081360587, 97.1047479057877, 94.99353147139327, 649 | 94.84864040280333, 95.39928771066917, 96.84026356095234, 93.43719982872705, 650 | 98.60821097335973, 94.02252407182942, 97.64789822483039, 90.80789425173771, 651 | 96.20229689904109, 96.19046017403522, 20.86476619751272, 95.97507085969605, 652 | 94.63077100231195, 94.9017946986088, 97.41124373384199, 98.54117366218806, 653 | 90.60409959361728, 96.92868960008381, 96.311349386401, 93.72641083658294, 654 | 98.19908174559103, 98.29758000817304, 93.82153089521309, 95.02306051785877, 655 | 98.15098621088046, 95.54619828274288, 97.66868425445557, 93.88253544232731, 656 | 98.60555238526351, 96.27294813005811, 2.7197781818795397, 657 | 49.338320402229215, 95.74397454280182, 96.13347495076377, 94.23821087167704, 658 | 99.17193281628053, 98.25834683173272, 93.40142601266308, 95.69563784985168, 659 | 93.88021551219781, 94.29978165990612, 96.51300237818523, 97.12275278511757, 660 | 95.48593346847534, 96.7767112858553, 98.14991805360546, 96.65759433251024, 661 | 49.25018970099806, 93.72596069486855, 19.00904582163302, 93.66766920452348, 662 | 99.63045319057709, 92.00135769781077, 24.669210067088777, 39.59515535841544, 663 | 91.14518626409487, 92.26000250534544, 94.77106851074556, 96.10232342160845, 664 | 91.640466996965, 97.90620599861968, 96.34548872498685, 100.0, 665 | 49.12996933073747, 95.84743156428087, 21.79138694615464, 99.43672093222621, 666 | 98.8415949519113, 97.442833649572, 15.640700214779796, 96.12648012748058, 667 | 89.98783953413536, 91.52228207137881, 98.62333493477325, 96.7894726183758, 668 | 94.00203467732258, 100.0, 89.05960870432553, 98.88134526839025, 669 | 98.73415827204919, 97.91755419225781, 100.0, 97.31216766928968, 670 | 97.70473380261402, 98.14444411007912, 90.77737618615195, 91.85700361787808, 671 | 96.30171265364389, 89.95270572633552, 93.47930448992139, 93.5889096047604, 672 | 93.73391913718815, 97.60505802054554, 98.52795027231348, 90.009680519378, 673 | 95.0769597981079, 90.04493633670367, 94.77324695291004, 96.79932919271188, 674 | 96.73176323597708, 91.74258774650214, 89.05629173568444, 93.85561494861687, 675 | 100.0, 88.79093611126585, 94.42823914321043, 5.928395667220676, 676 | 95.03560890542987, 92.31449491949034, 97.29274931379446, 96.68087735422003, 677 | 94.7379092150374, 94.92273000356897, 97.35083117706333, 93.80011408956466, 678 | 90.97047865969748, 94.94428940992147, 44.99031116212373, 95.43871399175136, 679 | 96.92663916093572, 98.16252058570933, 96.70070960602186, 99.17974579677816, 680 | 99.4460145241622, 93.90418191494354, 93.72120746082867, 96.17175472527352, 681 | 88.3507085751729, 91.74337516178728, 95.29978257836423, 98.21055340940764, 682 | 92.90097412762462, 18.497845389078677, 97.70499213371839, 92.54009635754748, 683 | 96.2091222152885, 92.90474532072402, 95.45120236551331, 91.18212316388045, 684 | 26.171793059184278, 91.53743300289509, 100.0, 93.83619003138399, 685 | 92.5750243914133, 94.63086237809118, 97.68462793869968, 93.02291303209745, 686 | 91.59020487396153, 87.89362690856451, 88.38444124277736, 93.44637253375755, 687 | 92.94029421538524, 97.26685424349759, 89.69719062556172, 93.87034071611698, 688 | 95.00095767165071, 97.09595864787303, 99.67226695791592, 94.24982650672426, 689 | 97.24300707394069, 100.0, 100.0, 94.16577304243027, 100.0, 690 | 94.57665684667982, 95.64059945328995, 95.21781583864937, 92.24684240867674, 691 | 95.2120953169822, 97.31454594161, 90.5231662182106, 92.72348037826858, 692 | 94.96128667313987, 12.707722387370968, 94.52851990449983, 100.0, 693 | 98.01100463919892, 94.19424811744261, 91.71821619832649, 90.60462599770297, 694 | 100.0, 97.04664325316253, 96.48970107169254, 100.0, 93.24853933278797, 695 | 92.0888275562789, 95.49435293011824, 97.23052261771306, 95.05667564741147, 696 | 100.0, 95.07115677161397, 94.29586556051727, 92.12321044188619, 697 | 94.60055674422149, 88.2093669247163, 93.57757330965846, 93.9678907550765, 698 | 10.67420973032574, 95.07956755744681, 97.48755509838607, 94.64254883010534, 699 | 93.05294501004836, 97.14747988990581, 95.15025932008191, 98.24267365509509, 700 | 98.64809305512375, 94.38328705208632, 95.30278352298063, 98.32659448102477, 701 | 96.98172116939479, 92.20188425144026, 92.72801221183762, 96.84876901271089, 702 | 94.15767853781023, 30.536939201235352, 97.93804934776064, 94.80579869383456, 703 | 94.39303293417069, 96.4809678617442, 95.74984168727045, 38.57119577636006, 704 | 95.98376150249456, 94.6823682427379, 97.21766646940921, 99.87952238042152, 705 | 99.55139108557476, 99.01648643515139, 95.29594839086751, 100.0, 706 | 93.17492177250735, 27.84457637941957, 89.80558106110854, 91.21475809831226, 707 | 95.81468207636026, 94.07136470228686, 96.34148210982042, 100.0, 708 | 98.10787444696871, 95.95500047593558, 89.24340558512543, 86.94650255026569, 709 | 96.25705999340228, 40.17947292807384, 91.76512339321279, 92.60501235956015, 710 | 92.87376821800532, 96.70109796804428, 89.66222371793228, 96.50667112537133, 711 | 89.42631651847452, 94.6637406543498, 95.62741614452862, 95.97363684452263, 712 | 97.68431374544747, 93.90176107977555, 88.93941218126676, 35.888380296310494, 713 | 99.71562196322738, 96.98980642972644, 100.0, 95.770287868982, 714 | 91.12954367090448, 99.24919691388001, 95.35446384882687, 93.88878850764972, 715 | 91.2333458441856, 97.10470230257171, 94.67023288611895, 98.45463300099469, 716 | 91.28223815199412, 98.50814050533477, 100.0, 92.67650846996513, 717 | 95.74066005482238, 98.45590925505476, 96.9208317148342, 99.35847731512547, 718 | 8.73973048081018, 97.40225336717994, 97.56475042361608, 100.0, 719 | 93.4447992885587, 98.93920381629647, 95.6206598772267, 91.95920862236633, 720 | 94.37963032848721, 99.27132894693801, 93.90703308907179, 87.57608920876741, 721 | 33.59301802570307, 96.55473917170238, 98.84561682471991, 91.63534587613975, 722 | 96.35361564107987, 91.72114495284285, 95.51583432821272, 93.92688877140348, 723 | 94.46482604887834, 93.03349376168795, 88.93813697904149, 91.1958145527632, 724 | 86.8608757534928, 97.49077387116823, 95.13396821344229, 95.44470356629692, 725 | 90.09689975358542, 88.15308882980304, 94.923665311999, 97.99145227683647, 726 | 98.10689494549028, 93.01917135880724, 98.63254354387038, 97.9890731847464, 727 | 96.75505058867925, 92.66620395309526, 90.80085100543373, 90.25882247991338, 728 | 95.02205533114041, 95.14180707862512, 93.97069716451666, 94.72661610987987, 729 | 98.76318377878988, 96.37675690150718, 95.97241343294375, 94.61815844560985, 730 | 94.5670468135485, 99.06668620128052, 98.69447766118827, 39.73468747189351, 731 | 100.0, 97.72228649509091, 92.18806484216731, 95.57192070760577, 732 | 90.61444463761234, 96.62523688449687, 100.0, 95.10720639811284, 733 | 94.26798875184232, 93.37525559226428, 100.0, 93.86399387951026, 734 | 97.11255584556312, 92.11319214851446, 98.60533003965169, 95.17384045557762, 735 | 95.66168465638647, 93.48774122518813, 93.76758300752269, 30.406781123673078, 736 | 95.28867527739393, 94.48572152875484, 100.0, 93.71000410548147, 100.0, 737 | 94.20989448193386, 93.23484222320887, 10.761650471660339, 99.76112105275733, 738 | 100.0, 88.28742971923526, 98.98771861486601, 91.73981265138917, 739 | 93.49085150009577, 96.94381292997056, 94.52520433931733, 93.01726168983949, 740 | 96.08111679726548, 98.2286911850856, 94.13534779023105, 97.03583319414177, 741 | 93.83144238885997, 94.97265462733831, 2.8567222187369126, 93.7912789296923, 742 | 93.21432339063506, 93.019461402722, 93.05534466525263, 99.49411381406907, 743 | 95.81715688370734, 97.86088451582218, 95.93096076228494, 97.07029765596923, 744 | 95.21001854336517, 91.63867977702657, 95.36170591461556, 98.34024804637286, 745 | 96.82723789698484, 91.51261757065186, 93.32846551416843, 97.1617045923648, 746 | 94.45059940872223, 96.34915761749363, 91.9339605249626, 93.5354936890613, 747 | 92.48646661555956, 88.93266132711666, 97.59292154632358, 90.96749884813303, 748 | 97.50322337967991, 93.74979975145207, 95.68902495702837, 95.2073091290542, 749 | 91.6195080112854, 44.599555451507015, 96.3634951964636, 98.99335906037727, 750 | 92.39113329709275, 25.510612532132292, 92.47047834707061, 93.06411075747029, 751 | 27.48852110859946, 87.67124693128451, 94.38340649658696, 0.5783763717658708, 752 | 96.50635653922859, 97.51840908574412, 93.15432299248451, 89.46616128615648, 753 | 25.64071579483044, 34.7255651447965, 94.34543061474706, 90.99414994392926, 754 | 96.36316396882796, 92.79783288825814, 88.8961736404378, 94.80354193831288, 755 | 94.32214131300078, 93.5570708151613, 93.23079507668193, 99.70918356621075, 756 | 97.14307953796693, 99.63088632260299, 91.71882957601383, 97.38689277241069, 757 | 94.14391257911412, 100.0, 98.31099741523744, 96.88651457857335, 758 | 90.68812819917528, 43.096826911701186, 98.08706552598817, 99.3332417708491, 759 | 90.40080557902867, 93.10283489614193, 9.213945204347606, 89.33073103115713, 760 | 92.88368680568237, 93.69332938883394, 94.53402192055557, 95.23764310297241, 761 | 92.94089981070627, 95.5215486646204, 97.1660016662863, 95.2891973771332, 762 | 91.91284837690452, 92.72245646964302, 98.41606960285304, 93.27630321840599, 763 | 94.17631768881938, 96.28466217422402, 97.9895085898527, 96.29293155997237, 764 | 97.79236932890738, 95.14471641714185, 92.74511935660028, 94.7060148589769, 765 | 95.91367212677767, 90.14701365079789, 93.82881281621636, 90.32962915090347, 766 | 94.63411769484111, 100.0, 91.74257094094364, 15.20878894661492, 100.0, 767 | 87.70763986734094, 93.19053664371644, 93.67256591868284, 93.07557701248258, 768 | 96.7307896298059, 99.44158234402443, 94.05795061810902, 93.6548354911183, 769 | 98.81061428750611, 92.16661164296745, 96.76618020397007, 98.5539927125807, 770 | 96.40403248151182, 93.4611029576935, 91.62835933199005, 91.06619186081971, 771 | 92.49653426050811, 99.0127018581732, 95.9799198633821, 45.911321523291534, 772 | 94.27880623523995, 99.38266896785206, 100.0, 95.16995304923768, 773 | 94.27657363757407, 96.33083486687819, 99.03037931655555, 97.5728145906007, 774 | 91.59716292277082, 92.34659215884746, 97.23130525279501, 100.0, 775 | 96.29926994397651, 95.90351666247061, 94.89451920415652, 94.67585029402302, 776 | 99.25129122983863, 96.00959030483138, 97.41103810385027, 97.52538875211143, 777 | 97.54997835886182, 91.68472193081708, 97.94684729840365, 95.64021027480833, 778 | 97.44462925792244, 93.78354729426003, 95.13612985910228, 93.9559369729459, 779 | 95.10015690141532, 94.7022444336782, 95.74508890185739, 94.34310424116971, 780 | 92.43063469673741, 99.98860515818691, 91.77177304648869, 96.45351964792583, 781 | 94.86193474087865, 42.6131670681779, 97.84317691556845, 93.1770151430153, 782 | 92.72905060294725, 96.20463783972995, 94.39138799337134, 94.05230313362397, 783 | 93.73903021789022, 92.80245755515244, 92.93354108562326, 100.0 784 | ], 785 | "counter_series": { 786 | "time": [ 787 | "2023-11-30T15:41:16.804876", 788 | "2023-11-30T15:43:21.804876", 789 | "2023-11-30T15:43:38.804876", 790 | "2023-11-30T15:44:22.804876", 791 | "2023-11-30T15:44:44.804876", 792 | "2023-11-30T15:44:44.804876", 793 | "2023-11-30T15:45:07.804876", 794 | "2023-11-30T15:45:31.804876", 795 | "2023-11-30T15:45:37.804876", 796 | "2023-11-30T15:45:38.804876", 797 | "2023-11-30T15:45:57.804876", 798 | "2023-11-30T15:45:58.804876", 799 | "2023-11-30T15:46:36.804876", 800 | "2023-11-30T15:46:44.804876", 801 | "2023-11-30T15:46:48.804876", 802 | "2023-11-30T15:47:18.804876", 803 | "2023-11-30T15:47:55.804876", 804 | "2023-11-30T15:48:50.804876", 805 | "2023-11-30T15:49:20.804876", 806 | "2023-11-30T15:49:30.804876", 807 | "2023-11-30T15:49:54.804876", 808 | "2023-11-30T15:50:15.804876", 809 | "2023-11-30T15:50:40.804876", 810 | "2023-11-30T15:51:47.804876", 811 | "2023-11-30T15:52:14.804876", 812 | "2023-11-30T15:53:08.804876", 813 | "2023-11-30T15:53:16.804876", 814 | "2023-11-30T15:54:49.804876", 815 | "2023-11-30T15:55:33.804876", 816 | "2023-11-30T15:55:41.804876" 817 | ], 818 | "counter": [ 819 | 3, 6, 14, 20, 27, 29, 35, 37, 38, 39, 41, 42, 46, 52, 53, 55, 62, 68, 75, 820 | 81, 84, 86, 93, 95, 97, 103, 105, 109, 111, 115 821 | ] 822 | } 823 | } 824 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * For a detailed explanation regarding each configuration property, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | import type {Config} from 'jest'; 7 | 8 | const config: Config = { 9 | // All imported modules in your tests should be mocked automatically 10 | // automock: false, 11 | 12 | // Stop running tests after `n` failures 13 | // bail: 0, 14 | 15 | // The directory where Jest should store its cached dependency information 16 | // cacheDirectory: "/private/var/folders/wm/p50xpf4n48g9bzmxmq9psj7m0000gn/T/jest_dx", 17 | 18 | // Automatically clear mock calls, instances, contexts and results before every test 19 | // clearMocks: false, 20 | 21 | // Indicates whether the coverage information should be collected while executing the test 22 | // collectCoverage: false, 23 | 24 | // An array of glob patterns indicating a set of files for which coverage information should be collected 25 | // collectCoverageFrom: undefined, 26 | 27 | // The directory where Jest should output its coverage files 28 | // coverageDirectory: undefined, 29 | 30 | // An array of regexp pattern strings used to skip coverage collection 31 | // coveragePathIgnorePatterns: [ 32 | // "/node_modules/" 33 | // ], 34 | 35 | // Indicates which provider should be used to instrument code for coverage 36 | coverageProvider: "v8", 37 | 38 | // A list of reporter names that Jest uses when writing coverage reports 39 | // coverageReporters: [ 40 | // "json", 41 | // "text", 42 | // "lcov", 43 | // "clover" 44 | // ], 45 | 46 | // An object that configures minimum threshold enforcement for coverage results 47 | // coverageThreshold: undefined, 48 | 49 | // A path to a custom dependency extractor 50 | // dependencyExtractor: undefined, 51 | 52 | // Make calling deprecated APIs throw helpful error messages 53 | // errorOnDeprecated: false, 54 | 55 | // The default configuration for fake timers 56 | // fakeTimers: { 57 | // "enableGlobally": false 58 | // }, 59 | 60 | // Force coverage collection from ignored files using an array of glob patterns 61 | // forceCoverageMatch: [], 62 | 63 | // A path to a module which exports an async function that is triggered once before all test suites 64 | // globalSetup: undefined, 65 | 66 | // A path to a module which exports an async function that is triggered once after all test suites 67 | // globalTeardown: undefined, 68 | 69 | // A set of global variables that need to be available in all test environments 70 | // globals: {}, 71 | 72 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 73 | // maxWorkers: "50%", 74 | 75 | // An array of directory names to be searched recursively up from the requiring module's location 76 | // moduleDirectories: [ 77 | // "node_modules" 78 | // ], 79 | 80 | // An array of file extensions your modules use 81 | // moduleFileExtensions: [ 82 | // "js", 83 | // "mjs", 84 | // "cjs", 85 | // "jsx", 86 | // "ts", 87 | // "tsx", 88 | // "json", 89 | // "node" 90 | // ], 91 | 92 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 93 | // moduleNameMapper: {}, 94 | 95 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 96 | // modulePathIgnorePatterns: [], 97 | 98 | // Activates notifications for test results 99 | // notify: false, 100 | 101 | // An enum that specifies notification mode. Requires { notify: true } 102 | // notifyMode: "failure-change", 103 | 104 | // A preset that is used as a base for Jest's configuration 105 | // preset: undefined, 106 | 107 | // Run tests from one or more projects 108 | // projects: undefined, 109 | 110 | // Use this configuration option to add custom reporters to Jest 111 | // reporters: undefined, 112 | 113 | // Automatically reset mock state before every test 114 | // resetMocks: false, 115 | 116 | // Reset the module registry before running each individual test 117 | // resetModules: false, 118 | 119 | // A path to a custom resolver 120 | // resolver: undefined, 121 | 122 | // Automatically restore mock state and implementation before every test 123 | // restoreMocks: false, 124 | 125 | // The root directory that Jest should scan for tests and modules within 126 | // rootDir: undefined, 127 | 128 | // A list of paths to directories that Jest should use to search for files in 129 | // roots: [ 130 | // "" 131 | // ], 132 | 133 | // Allows you to use a custom runner instead of Jest's default test runner 134 | // runner: "jest-runner", 135 | 136 | // The paths to modules that run some code to configure or set up the testing environment before each test 137 | // setupFiles: [], 138 | 139 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 140 | // setupFilesAfterEnv: [], 141 | 142 | // The number of seconds after which a test is considered as slow and reported as such in the results. 143 | // slowTestThreshold: 5, 144 | 145 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 146 | // snapshotSerializers: [], 147 | 148 | // The test environment that will be used for testing 149 | testEnvironment: "jsdom", 150 | 151 | // Options that will be passed to the testEnvironment 152 | // testEnvironmentOptions: {}, 153 | 154 | // Adds a location field to test results 155 | // testLocationInResults: false, 156 | 157 | // The glob patterns Jest uses to detect test files 158 | // testMatch: [ 159 | // "**/__tests__/**/*.[jt]s?(x)", 160 | // "**/?(*.)+(spec|test).[tj]s?(x)" 161 | // ], 162 | 163 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 164 | // testPathIgnorePatterns: [ 165 | // "/node_modules/" 166 | // ], 167 | 168 | // The regexp pattern or array of patterns that Jest uses to detect test files 169 | // testRegex: [], 170 | 171 | // This option allows the use of a custom results processor 172 | // testResultsProcessor: undefined, 173 | 174 | // This option allows use of a custom test runner 175 | // testRunner: "jest-circus/runner", 176 | 177 | // A map from regular expressions to paths to transformers 178 | "transform": { 179 | "\\.[jt]sx?$": ["babel-jest", { configFile: './babel.config.testing.js' }] 180 | }, 181 | 182 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 183 | // transformIgnorePatterns: [ 184 | // "/node_modules/", 185 | // "\\.pnp\\.[^\\/]+$" 186 | // ], 187 | 188 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 189 | // unmockedModulePathPatterns: undefined, 190 | 191 | // Indicates whether each individual test should be reported during the run 192 | // verbose: undefined, 193 | 194 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 195 | // watchPathIgnorePatterns: [], 196 | 197 | // Whether to use watchman for file crawling 198 | // watchman: true, 199 | }; 200 | 201 | export default config; 202 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | webpack(config) { 4 | config.resolve.extensions.push('.ts', '.tsx'); 5 | return config; 6 | } 7 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kube-sentry", 3 | "version": "1.0.0", 4 | "description": "Kubernetes Monitoring Toolkit", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon server/server.ts & next dev", 8 | "build": "NODE_ENV=production webpack & next build", 9 | "start": "npx ts-node server/server.ts & next start", 10 | "api": "nodemon server/server.ts", 11 | "lint": "next lint", 12 | "test": "jest" 13 | }, 14 | "author": "KubeSentry team", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "@babel/core": "^7.23.6", 18 | "@babel/preset-env": "^7.23.6", 19 | "@babel/preset-react": "^7.23.3", 20 | "@babel/preset-typescript": "^7.23.3", 21 | "@testing-library/jest-dom": "^6.1.5", 22 | "@testing-library/react": "^14.1.2", 23 | "@types/chance": "^1.1.6", 24 | "@types/express": "^4.17.21", 25 | "@types/jest": "^29.5.11", 26 | "@types/node": "^20.10.5", 27 | "@types/react": "^18.2.45", 28 | "@types/react-dom": "^18.2.18", 29 | "@types/supertest": "^2.0.16", 30 | "@types/uuid": "^9.0.7", 31 | "@types/vis": "^4.21.27", 32 | "autoprefixer": "^10.4.16", 33 | "babel-jest": "^29.7.0", 34 | "chance": "^1.1.11", 35 | "eslint": "^8.55.0", 36 | "eslint-config-next": "^14.0.3", 37 | "jest": "^29.7.0", 38 | "jest-environment-jsdom": "^29.7.0", 39 | "nodemon": "^3.0.2", 40 | "postcss": "^8.4.32", 41 | "supertest": "^6.3.3", 42 | "tailwindcss": "^3.3.5", 43 | "ts-jest": "^29.1.1", 44 | "ts-loader": "^9.5.1", 45 | "typescript": "^5.3.2", 46 | "webpack": "^5.89.0" 47 | }, 48 | "dependencies": { 49 | "@heroicons/react": "^2.0.18", 50 | "@kubernetes/client-node": "^0.20.0", 51 | "@types/cytoscape-cxtmenu": "^3.4.4", 52 | "@types/react-cytoscapejs": "^1.2.5", 53 | "chartjs-adapter-date-fns": "^3.0.0", 54 | "cytoscape": "^3.28.0", 55 | "cytoscape-cose-bilkent": "^4.1.0", 56 | "cytoscape-cxtmenu": "^3.5.0", 57 | "cytoscape-dom-node": "^1.2.0", 58 | "date-fns": "^2.30.0", 59 | "dotenv": "^16.3.1", 60 | "express": "^4.18.2", 61 | "next": "^14.0.3", 62 | "react": "^18.2.0", 63 | "react-chartjs-2": "^5.2.0", 64 | "react-cytoscapejs": "^2.0.0", 65 | "react-dom": "^18.2.0", 66 | "react-graph-vis": "^1.0.7", 67 | "ts-node": "^10.9.1", 68 | "uuid": "^9.0.1" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/SentryBot_sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KubeSentry/bbb642c65420a9914a8e4685a34f28516bf06619/public/SentryBot_sm.png -------------------------------------------------------------------------------- /public/graph_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KubeSentry/bbb642c65420a9914a8e4685a34f28516bf06619/public/graph_background.png -------------------------------------------------------------------------------- /public/kubesentrylogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KubeSentry/bbb642c65420a9914a8e4685a34f28516bf06619/public/kubesentrylogo.png -------------------------------------------------------------------------------- /public/node-relationship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/KubeSentry/bbb642c65420a9914a8e4685a34f28516bf06619/public/node-relationship.png -------------------------------------------------------------------------------- /scripts/setup.js: -------------------------------------------------------------------------------- 1 | const process = require('process'); 2 | 3 | 4 | ( async () => { 5 | console.log('Setting up databases.'); 6 | // Code for database init goes here. 7 | /* 8 | const Database = require('./server/models/database'); 9 | await Database.spinup(); 10 | await Database.disconnect(); 11 | */ 12 | process.exit(); 13 | })(); -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | const k8s = require('@kubernetes/client-node'); 2 | 3 | const kc = new k8s.KubeConfig(); 4 | kc.loadFromDefault(); 5 | const k8sApi = kc.makeApiClient(k8s.CoreV1Api); 6 | const metricsClient = new k8s.Metrics(kc); 7 | 8 | async function testk8s() { 9 | // loads the authentication data to our kubernetes object of our current cluster so it can talk to kube-apiserver 10 | // creates a kubernetes api client with our auth data. : This is what is doing the talking to the Kube-Apiserer. 11 | console.log('k8sApi: ', k8sApi); 12 | //metrics-server 13 | // console.log('metricsClient: ', metricsClient); 14 | const nodeMetrics = await metricsClient.getNodeMetrics(); 15 | // console.log('nodeMetrics: ', nodeMetrics); 16 | console.log('topNodes: ', await k8s.topNodes(k8sApi)); 17 | console.log('topPods: ', await k8s.topPods(k8sApi)); 18 | // console.log("nodes: ", (await k8sApi.listNode()).body) 19 | // console.log("nodes: ", (await k8sApi.listPod()).body) 20 | } 21 | 22 | (async () => { 23 | await testk8s(); 24 | process.exit(); 25 | })(); 26 | -------------------------------------------------------------------------------- /scripts/test_connection.js: -------------------------------------------------------------------------------- 1 | // require('dotenv').config(); 2 | // import {config} from 'dotenv' 3 | 4 | const k8s = require('@kubernetes/client-node'); 5 | 6 | const kc = new k8s.KubeConfig(); 7 | kc.loadFromDefault(); 8 | const k8sApi = kc.makeApiClient(k8s.CoreV1Api); 9 | const metricsClient = new k8s.Metrics(kc); 10 | 11 | async function testk8s() { 12 | // loads the authentication data to our kubernetes object of our current cluster so it can talk to kube-apiserver 13 | // creates a kubernetes api client with our auth data. : This is what is doing the talking to the Kube-Apiserer. 14 | console.log('k8sApi: ', k8sApi) 15 | //metrics-server 16 | console.log('metricsClient: ', metricsClient) 17 | const nodeMetrics = await metricsClient.getNodeMetrics(); 18 | console.log('nodeMetrics: ', nodeMetrics) 19 | console.log("topNodes: ", await k8s.topNodes(k8sApi)); 20 | // console.log("nodes: ", (await k8sApi.listNode()).body) 21 | console.log("nodes: ", (await k8sApi.listPod()).body) 22 | } 23 | 24 | 25 | /* 26 | console.log("Prometheus Port: ", process.env.PROMETHEUS_PORT) 27 | const promModel = require('../server/Models/prometheusModel') 28 | const PROM_HOST = `http://localhost:${process.env.PROMETHEUS_PORT}/api/v1`; 29 | async function testPrometheus() { 30 | const promModel = await import('../server/Models/prometheusModel') 31 | let dateString = promModel.buildDateString() 32 | console.log('dateString: ', dateString) 33 | const counter_data = await(promModel.runPromQuery('my_custom_counter{job="protest"}', dateString)) 34 | console.log('counter_data: ', counter_data) 35 | const dns_data = await promModel.runPromQuery('sum(rate(coredns_dns_requests_total{job=~"kubernetes-service-endpoints"}[5m]))', dateString) 36 | console.log('dns_data: ', dns_data) 37 | } 38 | */ 39 | 40 | // fetch from Prometheus 41 | 42 | 43 | ( async () => { 44 | await testk8s(); 45 | // await testPrometheus() 46 | process.exit(); 47 | })(); -------------------------------------------------------------------------------- /server/AlertLogs.txt: -------------------------------------------------------------------------------- 1 | ,{"receiver":"slack-receiver","status":"firing","alerts":[{"status":"firing","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-16T17:02:28.867Z","endsAt":"0001-01-01T00:00:00Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 2 | ,{"receiver":"slack-receiver","status":"resolved","alerts":[{"status":"resolved","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-16T17:02:28.867Z","endsAt":"2023-12-16T17:02:43.867Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 3 | ,{"receiver":"slack-receiver","status":"firing","alerts":[{"status":"firing","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-16T17:09:28.867Z","endsAt":"0001-01-01T00:00:00Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 4 | ,{"receiver":"slack-receiver","status":"resolved","alerts":[{"status":"resolved","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-16T17:09:28.867Z","endsAt":"2023-12-16T17:09:43.867Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 5 | ,{"receiver":"slack-receiver","status":"firing","alerts":[{"status":"firing","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-16T17:11:58.867Z","endsAt":"0001-01-01T00:00:00Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 6 | ,{"receiver":"slack-receiver","status":"resolved","alerts":[{"status":"resolved","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-16T17:11:58.867Z","endsAt":"2023-12-16T17:12:13.867Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 7 | ,{"receiver":"slack-receiver","status":"firing","alerts":[{"status":"firing","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-16T20:41:58.867Z","endsAt":"0001-01-01T00:00:00Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 8 | ,{"receiver":"slack-receiver","status":"resolved","alerts":[{"status":"resolved","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-16T20:41:58.867Z","endsAt":"2023-12-16T20:42:28.867Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 9 | ,{"receiver":"slack-receiver","status":"firing","alerts":[{"status":"firing","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-16T20:52:58.867Z","endsAt":"0001-01-01T00:00:00Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 10 | ,{"receiver":"slack-receiver","status":"resolved","alerts":[{"status":"resolved","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-16T20:52:58.867Z","endsAt":"2023-12-16T20:53:28.867Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 11 | ,{"receiver":"slack-receiver","status":"firing","alerts":[{"status":"firing","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-18T18:59:43.867Z","endsAt":"0001-01-01T00:00:00Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 12 | ,{"receiver":"slack-receiver","status":"resolved","alerts":[{"status":"resolved","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-18T18:59:43.867Z","endsAt":"2023-12-18T19:00:13.867Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 13 | ,{"receiver":"slack-receiver","status":"firing","alerts":[{"status":"firing","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-20T04:51:58.867Z","endsAt":"0001-01-01T00:00:00Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 14 | ,{"receiver":"slack-receiver","status":"resolved","alerts":[{"status":"resolved","labels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"annotations":{"summary":"wepage was accessed"},"startsAt":"2023-12-20T04:51:58.867Z","endsAt":"2023-12-20T04:52:28.867Z","generatorURL":"http://prometheus-server-646947dbff-m2m2c:9090/graph?g0.expr=increase%28my_custom_counter%5B45s%5D%29+%3E+0&g0.tab=1","fingerprint":"74e07e105f94f5f2"}],"groupLabels":{},"commonLabels":{"alertname":"accessed_webpage","instance":"10.1.0.131:3000","job":"mitch-test3","severity":"page"},"commonAnnotations":{"summary":"wepage was accessed"},"externalURL":"http://prometheus-alertmanager-0:9093","version":"4","groupKey":"{}:{}","truncatedAlerts":0} 15 | -------------------------------------------------------------------------------- /server/Controllers/kubeMetrics.ts: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | import { RequestHandler } from 'express'; 3 | 4 | import { PodStatusCount } from '../types/server-types'; 5 | import { k8sApi, k8sAppsApi, metricsClient, queryTopNodes } from '../Models/k8sModel'; 6 | import { PodItem } from '../../types/types'; 7 | 8 | import { 9 | NodeMetric, 10 | V1Pod, 11 | NodeStatus, 12 | topPods, 13 | topNodes 14 | } from '@kubernetes/client-node'; 15 | 16 | 17 | export const getNodes: RequestHandler = async (_, res, next) => { 18 | try { 19 | // console.log("Querying topNodes") 20 | queryTopNodes(); 21 | const listNode = await k8sApi.listNode(); 22 | res.locals.nodeList = listNode.body.items; 23 | return next(); 24 | } 25 | catch (err) { 26 | return next({ 27 | log: `error in getNodes: ${err}`, 28 | status: 500, 29 | message: { err: 'ERROR: unable to get top nodes.' }, 30 | }); 31 | } 32 | }; 33 | 34 | //get the node metrics 35 | export const getNodeMetrics: RequestHandler = async (_, res, next) => { 36 | try { 37 | res.locals.topNodes = await topNodes(k8sApi); 38 | res.locals.nodeMetrics = await metricsClient.getNodeMetrics(); 39 | return next(); 40 | } catch (err) { 41 | return next({ 42 | log: 'error in getNodeMetrics', 43 | status: 500, 44 | message: { err: 'could not get node metrics' }, 45 | }); 46 | } 47 | }; 48 | 49 | export const getServices: RequestHandler = async (_, res, next) => { 50 | try { 51 | const services = await k8sApi.listServiceForAllNamespaces(); 52 | res.locals.services = services.body.items; 53 | return next(); 54 | } catch (err) { 55 | return next({ 56 | log: `error in getServices: ${err}`, 57 | message: { err: 'ERROR: unable to list services.' }, 58 | }); 59 | } 60 | }; 61 | 62 | export const getDeployments: RequestHandler = async (_, res, next) => { 63 | try { 64 | const deployments = await k8sAppsApi.listDeploymentForAllNamespaces(); 65 | res.locals.deployments = deployments.body.items; 66 | return next(); 67 | } catch (err) { 68 | return next({ 69 | log: `error in getTopNodes: ${err}`, 70 | message: { err: 'ERROR: unable to list deployments.' }, 71 | }); 72 | } 73 | }; 74 | 75 | export const getKubeGraph: RequestHandler = async (_, res, next) => { 76 | // Namespaces? 77 | res.locals.nodes = k8sApi.listNode(); 78 | }; 79 | 80 | 81 | export const getRawPods: RequestHandler = async (_, res, next) => { 82 | try { 83 | // console.log('Trying to fetch pods.') 84 | const pods = await k8sApi.listPodForAllNamespaces(); 85 | // console.log('Got pods : ', pods.body) 86 | res.locals.rawPods = pods.body.items; 87 | return next(); 88 | } catch (err) { 89 | return next({ 90 | log: `error in getPodsRaw: ${err}`, 91 | message: { err: 'ERROR: unable to list pods.' }, 92 | }); 93 | } 94 | } 95 | 96 | 97 | //{name:{memused: , capacity: , percentage: } , name2:{...},...} 98 | export const getNodeMem: RequestHandler = async (_, res, next) => { 99 | // get the memory used for each node: [['name', 'mem(in Kb)'],...] 100 | const memUsed: [string, number][] = res.locals.nodeMetrics.items.map( 101 | (el: NodeMetric) => [ 102 | //name of node 103 | el.metadata.name, 104 | //memory usage of node comes in as '########ki' 105 | Number(el.usage.memory.slice(0, el.usage.memory.length - 2)), 106 | ] 107 | ); 108 | //get the memory capacity of each node (in Mb) 109 | const memCap = res.locals.topNodes.map((el: NodeStatus) => 110 | Number(el.Memory.Capacity) 111 | ); 112 | //initialize the result object 113 | res.locals.nodeMem = {}; 114 | //populate the result object 115 | for (let i = 0; i < memUsed.length; i++) { 116 | res.locals.nodeMem[memUsed[i][0]] = { 117 | 'memUsed(kb)': memUsed[i][1], 118 | capacity: memCap[i], 119 | percentage: ((memUsed[i][1] / memCap[i]) * 100000).toFixed(2), 120 | }; 121 | } 122 | return next(); 123 | }; 124 | 125 | //[{namespace:, pod-name: , status: },...] 126 | export const getPods: RequestHandler = async (_, res, next) => { 127 | try { 128 | //get all the pods from our cluster 129 | const podsRes = await k8sApi.listPodForAllNamespaces() 130 | //ARRAY OF ['namespace', 'pod-name', 'status] 131 | const resPods: PodItem[] = []; 132 | const statusCount: PodStatusCount = {}; 133 | const nameSpaces = new Set(); 134 | //pod objects 135 | podsRes.body.items.forEach((el: V1Pod) => { 136 | if (el.metadata && el.status) { 137 | // Todo: find a better way to handle undefined values 138 | const status: string = el.status.phase!; 139 | // Keep track of pod counts by status bucket 140 | statusCount[status] = ++statusCount[status] || 1; 141 | //running containers 142 | const containers = el.spec!.containers!; 143 | const amtOfContainers = containers.length; 144 | resPods.push({ 145 | namespace: el.metadata.namespace!, 146 | name: el.metadata.name!, 147 | status, 148 | creationTimestamp: el.metadata.creationTimestamp!, 149 | dnsPolicy: el.spec!.dnsPolicy!, 150 | containers: amtOfContainers, 151 | restartPolicy: el.spec!.restartPolicy!, 152 | hostIP: el.status!.hostIP!, 153 | podIP: el.status!.podIP!, 154 | startTime: el.status!.startTime!, 155 | }); 156 | nameSpaces.add(el.metadata.namespace || 'default'); 157 | } else { 158 | // Not handled 159 | return; 160 | } 161 | }); 162 | 163 | //array of namespaces 164 | res.locals.pods = { 165 | pods: resPods, 166 | nameSpace: [...nameSpaces], 167 | statusCount, 168 | }; 169 | return next(); 170 | } catch (err) { 171 | return next({ 172 | log: 'could not get pods from middleware', 173 | status: 400, 174 | message: { err }, 175 | }); 176 | } 177 | }; 178 | 179 | export const getPodMetrics: RequestHandler = async (_, res, next) => { 180 | try { 181 | res.locals.topPods = await topPods(k8sApi, metricsClient); 182 | res.locals.podMetrics = await metricsClient.getPodMetrics(); 183 | return next(); 184 | } catch (err) { 185 | return next({ 186 | log: 'could not get pod Metrics from middleware', 187 | status: 400, 188 | message: { err }, 189 | }); 190 | } 191 | }; 192 | 193 | export const deletePod: RequestHandler = async (req, res, next) => { 194 | try { 195 | //extract name and namespace from endpoint 196 | const { name, namespace } = req.params; 197 | //delete pod using kubernetes client method 198 | res.locals.deletedpod = await k8sApi.deleteNamespacedPod( 199 | name, 200 | namespace, 201 | undefined, 202 | undefined, 203 | 2 204 | ); 205 | // move to next middleware 206 | return next(); 207 | } catch (err) { 208 | // route to global error handler 209 | return next({ 210 | log: 'could not get pod Metrics from middleware', 211 | status: 400, 212 | message: { err }, 213 | }); 214 | } 215 | }; 216 | 217 | //readNamespacedEvent 218 | //listNamespacedEvent 219 | export const podEvents: RequestHandler = async (req, res, next) => { 220 | const { name, namespace } = req.params; 221 | res.locals.podEvents = await k8sApi.listNamespacedEvent( 222 | namespace, 223 | undefined, 224 | undefined 225 | ); 226 | 227 | //filter the V1PodEvent response by the involved object name 228 | res.locals.podEvents = res.locals.podEvents.body.items.filter( 229 | (el: any) => el.involvedObject.name == name 230 | ); 231 | return next(); 232 | }; 233 | 234 | export const kubeLogs: RequestHandler = async (req, res, next) => { 235 | const { name, namespace } = req.params; 236 | res.locals.podLogs = await k8sApi.readNamespacedPodLog(name, namespace); 237 | return next(); 238 | }; 239 | -------------------------------------------------------------------------------- /server/Controllers/logsController.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { RequestHandler } from 'express'; 3 | import path from 'path'; 4 | 5 | //path to the alert logs 6 | const alertPath = path.join(__dirname, '../AlertLogs.txt'); 7 | 8 | //add alert to the database 9 | export const addAlertLogs: RequestHandler = async (req, res, next) => { 10 | const message = JSON.stringify(req.body); 11 | // console.log(message); 12 | try { 13 | fs.appendFileSync(alertPath, `,${message}\n`); 14 | let data = fs.readFileSync(alertPath, 'utf-8'); 15 | data = data.slice(1).replace(/\n/g, ''); 16 | res.locals.alerts = JSON.parse(`[${data.trim()}]`); 17 | return next(); 18 | } catch (err) { 19 | return next({ 20 | log: 'error in getting alert logs', 21 | status: 500, 22 | message: { err: 'unexpected error' }, 23 | }); 24 | } 25 | }; 26 | 27 | //read the alerts log database: send response as an array 28 | export const getAlertLogs: RequestHandler = async (req, res, next) => { 29 | try { 30 | let data = fs.readFileSync(alertPath, 'utf-8'); 31 | data = data.slice(1).replace(/\n/g, ''); 32 | res.locals.alerts = JSON.parse(`[${data.trim()}]`); 33 | return next(); 34 | } catch (err) { 35 | return next({ 36 | log: 'error in getting alert logs', 37 | status: 500, 38 | message: { err: 'unexpected error' }, 39 | }); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /server/Controllers/promMetrics.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from 'express'; 2 | import { runPromQuery, buildDateString } from '../Models/prometheusModel' 3 | 4 | const defaultRange = buildDateString(); 5 | 6 | export const getCustomCounter: RequestHandler = async (_, res, next) => { 7 | try { 8 | 9 | const counter_result = await(runPromQuery('my_custom_counter', defaultRange)) 10 | 11 | res.locals.counterData = { 12 | metric: counter_result.data.result[0].metric.__name__, 13 | values: counter_result.data.result[0].values, 14 | }; 15 | //go to next middleware 16 | return next(); 17 | } catch (err) { 18 | //route to global error 19 | return next({ 20 | log: `error in promQuery. Error: ${err}`, 21 | status: 400, 22 | message: { err: 'could not get prom data' }, 23 | }); 24 | } 25 | }; 26 | 27 | // Executes the Prometheus query contained in req.query.query. 28 | // Stores the result to res.locals.counterData. 29 | export const getPrometheusMetrics: RequestHandler = async (req, res, next) => { 30 | 31 | try { 32 | if (typeof req.query.query != 'string') { 33 | throw new Error("Unknown value for 'query'") 34 | } 35 | const result = await runPromQuery(req.query.query, defaultRange); 36 | res.locals.counterData = { 37 | metric: result.data.result[0].metric.__name__, 38 | values: result.data.result[0].values, 39 | }; 40 | // TODO: check whether we got something usable and fail gracefully 41 | 42 | // go to next middleware 43 | return next(); 44 | } catch (err) { 45 | //route to global error 46 | return next({ 47 | log: `error in promQuery: ${err}`, 48 | status: 400, 49 | message: { err: 'could not get prom data' }, 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /server/Models/k8sModel.ts: -------------------------------------------------------------------------------- 1 | //creates a Kubernetes cluster object 2 | import { 3 | KubeConfig, 4 | CoreV1Api, 5 | Metrics, 6 | AppsV1Api, 7 | topNodes 8 | } from '@kubernetes/client-node'; 9 | 10 | const kc = new KubeConfig(); 11 | //loads the authentication data to our kubernetes object of our current cluster so it can talk to kube-apiserver 12 | kc.loadFromDefault(); 13 | //creates a kubernetes api client with our auth data. : This is what is doing the talking to the Kube-Apiserer. 14 | export const k8sApi = kc.makeApiClient(CoreV1Api); 15 | // For 16 | export const k8sAppsApi = kc.makeApiClient(AppsV1Api) 17 | //mertics-server 18 | export const metricsClient = new Metrics(kc); 19 | 20 | export const queryTopNodes = async () => { 21 | // console.log('Querying topNodes: '); 22 | return await topNodes(k8sApi) 23 | } -------------------------------------------------------------------------------- /server/Models/prometheusModel.ts: -------------------------------------------------------------------------------- 1 | 2 | import { config } from 'dotenv'; 3 | import { PrometheusResponse } from '../types/server-types'; 4 | config(); 5 | const PROM_HOST = `http://localhost:${process.env.PROMETHEUS_PORT}/api/v1`; 6 | 7 | console.log("PROM_HOST: ", PROM_HOST) 8 | 9 | 10 | 11 | // Build date string for Prometheus 12 | export function buildDateString(range=60, step=60): string { 13 | // 1 hour range by default 14 | let stepString = `${step}s`; 15 | //current time 16 | const end = new Date(); 17 | //range of query (mintues) :1min * 1000ms/s * 60s/min 18 | const start = new Date(); 19 | start.setMinutes(end.getMinutes()-range) 20 | //range query string to append to base prom fetch 21 | return `start=${start.toISOString()}&end=${end.toISOString()}&step=${stepString}`; 22 | } 23 | 24 | 25 | // Send a Prometheus API query using the following URL format: 26 | // http://url:PORT/api/v1/query_range?query={query}&start={}&end={}&step 27 | // @params {query}: Prometheus query (string) 28 | // @params {dateString}: start, end, step query parameters (string) 29 | // @returns PrometheusResponse 30 | export async function runPromQuery(query: string, dateString: string) { 31 | const promQuery = `${PROM_HOST}/query_range?query=${query}&${dateString}`; 32 | // console.log(`Query: ${promQuery}`) 33 | const data = await fetch( promQuery ); 34 | const result: PrometheusResponse = await data.json(); 35 | return result; 36 | } -------------------------------------------------------------------------------- /server/Routers/metricsRouter.ts: -------------------------------------------------------------------------------- 1 | import { Router, Request, Response } from 'express'; 2 | const router = Router(); 3 | 4 | /**********************IMPORT CONTROLLERS**************** */ 5 | import { 6 | getNodes, 7 | getNodeMetrics, 8 | getNodeMem, 9 | getPods, 10 | getRawPods, 11 | getServices, 12 | getDeployments, 13 | deletePod, 14 | podEvents, 15 | kubeLogs, 16 | } from '../Controllers/kubeMetrics'; 17 | import { getCustomCounter, getPrometheusMetrics } from '../Controllers/promMetrics'; 18 | import { KubeGraphData } from '../../types/types' 19 | 20 | import { getAlertLogs, addAlertLogs } from '../Controllers/logsController'; 21 | /**********************ROUTE ACTIONS**************** */ 22 | //time series query : http://localhost:31302/api/v1/query_range?query=&start=&end=&step 23 | //job query: query?query={job=''} 24 | 25 | // Forward Prometheus data 26 | router.get('/prom', 27 | getPrometheusMetrics, 28 | (_, res: Response) => { 29 | res.status(200).json(res.locals.counterData); 30 | }); 31 | 32 | // MIGHT HAVE TO REFRESH A FEW TIMES TO GET DATA. IT WORKS I SWEAR!!! 33 | // prometheus data for our webapp(for now) 34 | router.get('/customCounter', getCustomCounter, (_, res: Response) => { 35 | res.status(200).json(res.locals.counterData); 36 | }); 37 | 38 | //Kubernetes node information 39 | router.get( 40 | '/kubeNodes', 41 | getNodeMetrics, 42 | getNodeMem, 43 | (_, res) => { 44 | res.status(200).json(res.locals.result); 45 | } 46 | ); 47 | 48 | router.get( 49 | '/kubeGraph', 50 | getNodes, 51 | getRawPods, 52 | getServices, 53 | getDeployments, 54 | (_, res) => { 55 | const graph: KubeGraphData = { 56 | nodeList: res.locals.nodeList, 57 | pods: res.locals.rawPods, 58 | services: res.locals.services, 59 | deployments: res.locals.deployments 60 | } 61 | res.status(200).json(graph); 62 | } 63 | ); 64 | 65 | //kubernetes pod information 66 | router.get('/kubePods', getPods, (_, res) => { 67 | res.status(200).json(res.locals.pods); 68 | }); 69 | 70 | // delete a pod from cluster 71 | router.get('/delete/:namespace/:name', deletePod, (req, res) => { 72 | res.status(200).json(res.locals.deletedpod); 73 | }); 74 | 75 | //get a pods logs and events 76 | router.get('/getEvents/:namespace/:name', podEvents, kubeLogs, (req, res) => { 77 | res 78 | .status(200) 79 | .json({ events: res.locals.podEvents, logs: res.locals.podLogs.body }); 80 | }); 81 | 82 | router.get('/alertlogs', getAlertLogs, (req, res) => { 83 | res.status(200).json(res.locals.alerts); 84 | }); 85 | 86 | router.post('/alertlogs', addAlertLogs, (req, res) => { 87 | res.status(200).json(res.locals.alerts); 88 | }); 89 | /**********************EXPORT ROUTER**************** */ 90 | export default router; 91 | -------------------------------------------------------------------------------- /server/index.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NodeStatus, 3 | PodStatus, 4 | PodMetricsList, 5 | NodeMetricsList, 6 | } from '@kubernetes/client-node'; 7 | import { 8 | PodItem, 9 | NodeMemValue, 10 | PrometheusDataItem, 11 | } from './types/server-types'; 12 | import { PromMetricsData } from '../types/types'; 13 | 14 | declare global { 15 | namespace Express { 16 | export interface Locals { 17 | pods: { 18 | pods: PodItem[]; 19 | nameSpace: string[]; 20 | statusCount: PodStatusCount; 21 | }; 22 | rawPods: V1Pod[], 23 | topPods: PodStatus[]; 24 | topNodes: NodeStatus[]; 25 | podMetrics: PodMetricsList; 26 | nodeMetrics: NodeMetricsList; 27 | nodeMem: Record, 28 | prometheusData: PrometheusBackendResponse; 29 | counterData: PromMetricsData; 30 | services: V1ServiceList, 31 | deployments: V1DeplymentList 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /server/server.ts: -------------------------------------------------------------------------------- 1 | import express, {Application, Request, Response, ErrorRequestHandler } from 'express'; 2 | import { ServerError } from './types/server-types' 3 | const app:Application = express(); 4 | const PORT = 8888; 5 | 6 | app.use(express.json()); 7 | /**************************IMPORT ROUTERS********************************** */ 8 | 9 | import metricsRouter from './Routers/metricsRouter'; 10 | 11 | /**************************SERVING STATIC FILES**************************** */ 12 | //NONE BECAUSE WE ARE USING NEXT JS AS A SECONDARY SERVER 13 | /**************************ENPOINT ACTIONS********************************* */ 14 | 15 | app.use('/metrics', metricsRouter); 16 | 17 | /**************************404 HANDLER********************************** */ 18 | app.use('*', (_: Request, res: Response): void => { 19 | res.status(404).send('unknown location'); 20 | }); 21 | /**************************GLOBAL ERROR HANDLER********************************** */ 22 | 23 | const errHandler: ErrorRequestHandler = (err, _, res, __) => { 24 | const defaultErr: ServerError = { 25 | log: 'error in some middleware', 26 | status: 500, 27 | message: { err: 'unexpected error' }, 28 | }; 29 | //using the default error object as base, create a customized error object 30 | const errorObj: ServerError = Object.assign(defaultErr, err); 31 | console.log(errorObj.log); 32 | res.status(errorObj.status).json(errorObj.message); 33 | 34 | } 35 | app.use(errHandler); 36 | 37 | 38 | if (process.env.NODE_ENV !== 'test') { 39 | app.listen(PORT, () => { 40 | console.log(`Kube Sentry API server opened on port ${PORT}`); 41 | }); 42 | } 43 | 44 | // For testing 45 | export default app; -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2016", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "noImplicitAny": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "NodeNext", 16 | "moduleResolution": "NodeNext", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true, 21 | "plugins": [ 22 | { 23 | "name": "next" 24 | } 25 | ], 26 | "paths": { 27 | "*": [ 28 | "../types/server-types.ts" 29 | ], 30 | "@/*": [ 31 | "./*" 32 | ] 33 | } 34 | }, 35 | "include": [ 36 | "**/*.ts", 37 | "**/*.tsx", 38 | "server.d.ts", 39 | "./types/server-types.ts" 40 | ], 41 | "exclude": [ 42 | "node_modules" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /server/types/server-types.ts: -------------------------------------------------------------------------------- 1 | // https://prometheus.io/docs/prometheus/latest/querying/api/ 2 | import { PrometheusDataItem } from '../..//types/types'; 3 | 4 | export type ServerError = { 5 | log: string; 6 | status: number; 7 | message: { err: string }; 8 | }; 9 | 10 | export interface PodStatusCount { 11 | [key: string]: number; 12 | } 13 | export interface NodeMemValue { 14 | 'memUsed(kb)': number; 15 | capacity: number; 16 | percentage: string; 17 | } 18 | 19 | type PrometheusRangeVector = { 20 | metric: Record; 21 | values: PrometheusDataItem[]; 22 | }; 23 | 24 | 25 | interface PrometheusData { 26 | resultType: ("matrix" | "vector" | "scalar" | "string"), 27 | result: PrometheusRangeVector[]; 28 | } 29 | 30 | export interface PrometheusResponse { 31 | status: 'success' | 'error'; 32 | data: PrometheusData; 33 | 34 | // Only set if status is "error". The data field may still hold 35 | // additional data. 36 | errorType: string; 37 | error: string; 38 | 39 | // Only if there were warnings while executing the request. 40 | // There will still be data in the data field. 41 | warnings: string[]; 42 | } 43 | -------------------------------------------------------------------------------- /src/app/api/graph/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { KubeGraphData } from '@/types/types' 3 | 4 | export async function GET() { 5 | // console.log('Fetching /api/graph/') 6 | let res = await fetch('http://localhost:8888/metrics/kubeGraph', { 7 | next: { revalidate: 30 } 8 | }); 9 | let data: KubeGraphData = await res.json(); 10 | // console.log("Raw data: ", data) 11 | return NextResponse.json(data); 12 | } -------------------------------------------------------------------------------- /src/app/api/podStatus/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { KubePodsStatus } from '@/types/types' 3 | 4 | export async function GET(request: Request): Promise { 5 | let res = await fetch('http://localhost:8888/metrics/kubePods', { 6 | cache: 'no-store', 7 | }); 8 | let data: KubePodsStatus = await res.json(); 9 | // console.log("Raw data: ", data) 10 | return NextResponse.json(data.statusCount); 11 | } -------------------------------------------------------------------------------- /src/app/api/promQuery/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | import { PromMetricsData } from '../../../../types/types' 3 | 4 | export async function GET(request: NextRequest) { 5 | let res = await fetch(`http://localhost:8888/metrics/prom?query=${request.nextUrl.searchParams.get("query")}`, { 6 | cache: 'no-store', 7 | }); 8 | let data: PromMetricsData = await res.json(); 9 | return NextResponse.json(data); 10 | } 11 | -------------------------------------------------------------------------------- /src/app/charts/LinePlot.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | Chart as ChartJS, 5 | CategoryScale, 6 | LinearScale, 7 | PointElement, 8 | LineElement, 9 | Title, 10 | Tooltip, 11 | Legend, 12 | } from 'chart.js'; 13 | 14 | ChartJS.register( 15 | CategoryScale, 16 | LinearScale, 17 | PointElement, 18 | LineElement, 19 | Title, 20 | Tooltip, 21 | Legend 22 | ); 23 | 24 | import { Line } from 'react-chartjs-2'; 25 | import mock_data from "../../../build/mock_data.json"; 26 | 27 | 28 | function LinePlotDisplay (props: any) { 29 | const {data} = props; 30 | const graphTextColor = 'rgba(255,255,255,0.75)'; 31 | const yAxisTitle = 'Data'; 32 | 33 | const options = { 34 | plugins: { 35 | legend: { 36 | labels: { 37 | color: graphTextColor 38 | }, 39 | display: true 40 | } 41 | }, 42 | responsive: true, 43 | animation: false as const, 44 | scales: { 45 | y: { 46 | ticks: { 47 | color: graphTextColor 48 | }, 49 | grid: { 50 | display: true, 51 | color: 'rgba(128, 128, 128, 0.1)' 52 | }, 53 | display: true, 54 | title: { 55 | display: true, 56 | text: yAxisTitle, 57 | color: graphTextColor 58 | } 59 | }, 60 | x: { 61 | ticks: { 62 | color: graphTextColor 63 | }, 64 | grid: { 65 | display: false, 66 | color: 'rgba(128, 128, 128, 0.1)' 67 | }, 68 | display: true, 69 | title: { 70 | display: true, 71 | text: 'Observation', 72 | color: 'rgba(255, 255, 255, 0.702)' 73 | } 74 | } 75 | } 76 | } 77 | 78 | 79 | const dataSet = { 80 | labels: [...data.keys()], 81 | datasets: [{ 82 | label: 'Sequential Dataset', 83 | data: data, 84 | fill: false, 85 | borderColor: 'rgb(75, 192, 192)', 86 | }] 87 | }; 88 | 89 | return ( ) 90 | }; 91 | 92 | 93 | 94 | export default function LinePlot() { 95 | // Pull in data from somewhere else 96 | return 97 | 98 | } 99 | 100 | -------------------------------------------------------------------------------- /src/app/charts/PieChart.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; 4 | import { useEffect, useState, useRef } from 'react'; 5 | import { Doughnut } from 'react-chartjs-2'; 6 | import { objCompare } from '../../util/utils' 7 | import { PieChartData } from '../../../types/types' 8 | 9 | ChartJS.register(ArcElement, Tooltip, Legend); 10 | 11 | export default function PieChart() { 12 | /***************************************USE STATE************************************************* */ 13 | const [piedata, setPieData] = useState({}); 14 | const didMountRef = useRef(false); 15 | 16 | /************************************HELPER FUNCTIONS*********************************************** */ 17 | 18 | 19 | //async function to get the data from thebackend 20 | async function getPieData() { 21 | try { 22 | // console.log('start getting data'); 23 | //do not cache the fetch request. 24 | let response = await fetch('/api/podStatus', { 25 | cache: 'no-store', 26 | }); 27 | //convert to js readable 28 | let jsondata: PieChartData = await response.json(); 29 | //return the js readable data 30 | return jsondata; 31 | } catch (err) { 32 | //error catch 33 | console.log(`Error: ${err}`); 34 | } 35 | } 36 | 37 | useEffect(() => { 38 | // Skip on initial render 39 | if (!didMountRef.current) { 40 | didMountRef.current = true; 41 | return; 42 | } 43 | // Set up interval for your logic (e.g., fetching data) 44 | const id = setInterval(() => { 45 | getPieData().then((data) => { 46 | if (data) { 47 | // console.log('Number of pods', data) 48 | if (!objCompare(data, piedata)) { 49 | setPieData(Object.assign({}, data)); 50 | } 51 | } 52 | }); 53 | }, 2000); 54 | 55 | // Clean up the interval when the component is unmounted or when the effect runs again 56 | return () => { 57 | clearInterval(id); 58 | }; 59 | // dependent on the data 60 | }, [piedata]); 61 | 62 | const options = { 63 | animation: false, 64 | }; 65 | 66 | const chartData = { 67 | labels: Object.keys(piedata), 68 | datasets: [ 69 | { 70 | data: Object.keys(piedata).map((c) => piedata[c]), 71 | backgroundColor: [ 72 | 'rgba(124,252,0,0.6)', 73 | 'rgb(250,128,114)', 74 | ], 75 | borderColor: [ 76 | 'rgba(99, 235, 102, 1)', 77 | 'rgba(54, 162, 235, 1)', 78 | 'rgba(255, 206, 86, 1)', 79 | 'rgba(75, 192, 192, 1)', 80 | 'rgba(153, 102, 255, 1)', 81 | 'rgba(255, 159, 64, 1)', 82 | ], 83 | borderWidth: 1, 84 | }, 85 | ], 86 | }; 87 | 88 | //render the pie chart 89 | return ; 90 | } 91 | -------------------------------------------------------------------------------- /src/app/charts/TimeSeriesPlot.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { PromMetricsData } from '../../../types/types'; 3 | 4 | import 'chartjs-adapter-date-fns'; 5 | import { useState, useEffect, useRef } from 'react'; 6 | 7 | 8 | import { 9 | Chart as ChartJS, 10 | CategoryScale, 11 | LinearScale, 12 | TimeScale, 13 | PointElement, 14 | LineElement, 15 | Title, 16 | Tooltip, 17 | Legend, 18 | } from 'chart.js'; 19 | 20 | ChartJS.register( 21 | CategoryScale, 22 | LinearScale, 23 | PointElement, 24 | LineElement, 25 | TimeScale, 26 | Title, 27 | Tooltip, 28 | Legend 29 | ); 30 | 31 | import { Line } from 'react-chartjs-2'; 32 | 33 | type timedata = { 34 | time: Date[], 35 | counter: number[], 36 | title: string, 37 | }; 38 | 39 | //type error on options param 40 | function TimeSeriesPlotDisplay({ counter, time, title }) { 41 | const graphTextColor = 'rgba(255,255,255,0.75)'; 42 | const chartRef = useRef(null); 43 | 44 | const options = { 45 | plugins: { 46 | legend: { 47 | labels: { 48 | color: graphTextColor, 49 | }, 50 | display: true, 51 | }, 52 | }, 53 | responsive: true, 54 | elements: { 55 | point: { 56 | pointStyle: false 57 | } 58 | }, 59 | scales: { 60 | y: { 61 | ticks: { 62 | color: graphTextColor, 63 | }, 64 | grid: { 65 | display: true, 66 | color: 'rgba(128, 128, 128, 0.1)', 67 | }, 68 | display: true, 69 | title: { 70 | display: true, 71 | // text: data.title, 72 | color: graphTextColor, 73 | }, 74 | }, 75 | x: { 76 | ticks: { 77 | maxRotation: 20, 78 | color: graphTextColor, 79 | }, 80 | grid: { 81 | display: false, 82 | color: 'rgba(128, 128, 128, 0.1)', 83 | }, 84 | display: true, 85 | title: { 86 | display: true, 87 | text: 'Time', 88 | color: 'rgba(255, 255, 255, 0.702)', 89 | }, 90 | type: 'time' as const, 91 | time: { 92 | unit: 'second' as const, 93 | displayFormats: { 94 | second: 'HH:mm:ss', 95 | }, 96 | }, 97 | }, 98 | }, 99 | }; 100 | 101 | const dataSet = { 102 | labels: time, 103 | datasets: [ 104 | { 105 | label: title, 106 | data: counter, 107 | fill: false, 108 | borderColor: 'rgb(75, 192, 192)', 109 | }, 110 | 111 | ], 112 | 113 | }; 114 | 115 | return ; 116 | } 117 | 118 | type TimeSeriesParams = {query: string, title: string}; 119 | export default function TimeSeriesPlot(params: TimeSeriesParams) { 120 | const [time, setTime] = useState([]); 121 | const [counter, setCounter] = useState([]); 122 | 123 | useEffect(() => { 124 | const id = setInterval(async() => { 125 | const response = await fetch(`/api/promQuery?query=${params.query}`, { 126 | cache: 'no-store', 127 | }); 128 | let jsondata: PromMetricsData = await response.json(); 129 | const { values } = jsondata; 130 | //only update if we get something back from prometheus 131 | //do this way to not mutate the state 132 | const newTime: Date[] = []; 133 | const newCounter: number[] = []; 134 | if (values) { 135 | values.forEach((el: [number, string]) => { 136 | newTime.push(new Date(el[0] * 1000)); 137 | newCounter.push(Number(el[1])); 138 | }); 139 | // console.log('Got values: ', values) 140 | setTime(newTime); 141 | setCounter(newCounter); 142 | } 143 | }, 5000); 144 | //cleanup code 145 | return () => clearInterval(id); 146 | }, [counter, time]); 147 | 148 | return ( 149 | 150 | ) 151 | 152 | // commented out this {TimeSeriesPlotDisplay({ time={time}, counter={counter}, title: params.title })}; 153 | } 154 | 155 | -------------------------------------------------------------------------------- /src/app/components/Banner.tsx: -------------------------------------------------------------------------------- 1 | import { XMarkIcon } from '@heroicons/react/20/solid' 2 | import { MouseEventHandler } from 'react'; 3 | 4 | type BannerProps = { closeBanner: MouseEventHandler }; 5 | 6 | 7 | export default function Banner(props: BannerProps) { 8 | const { closeBanner } = props; 9 | return ( 10 |
11 |