├── .DS_Store
├── .babelrc
├── .dockerignore
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── __mocks__
└── fileMock.js
├── __tests__
├── configController.test.js
├── kubeController.test.js
└── prometheusController.test.js
├── babel.config.js
├── bundle.js
├── client
├── assets
│ ├── Aldrich
│ │ ├── Aldrich-Regular.ttf
│ │ └── OFL.txt
│ ├── halfLogo.png
│ ├── logo.png
│ ├── logo2.png
│ ├── logoName.png
│ └── slogan.png
└── components
│ ├── Graph.jsx
│ ├── GraphsContainer.jsx
│ ├── Navbar.jsx
│ ├── Parameter.jsx
│ ├── ParameterContainer.jsx
│ ├── RestartedPodRow.jsx
│ ├── RestartedPodTable.jsx
│ ├── SavedConfig.jsx
│ ├── TimeInput.jsx
│ └── ToolTip.jsx
├── deployment.yaml
├── docker-compose-dev-hot.yml
├── electron.js
├── index.html
├── jest.setup.js
├── main.js
├── main.jsx
├── package-lock.json
├── package.json
├── server
├── controllers
│ ├── configController.js
│ ├── kubeController.js
│ └── prometheusController.js
├── router.js
└── services
│ └── prometheusService.js
├── service.yaml
├── style.css
└── webpack.config.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/PodMD/56a0593a5c73316d4624976519606d64f8627829/.DS_Store
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"],
3 | "plugins": ["@babel/plugin-transform-modules-commonjs"]
4 | }
5 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 | node_modules/
10 | build/
11 | dist
12 | dist-ssr
13 | *.local
14 | eks-cluster-role-trust-policy.json
15 | .DS_Store
16 | # Editor directories and files
17 | .vscode/*
18 | !.vscode/extensions.json
19 | .idea
20 | .DS_Store
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts-alpine
2 | RUN npm install -g webpack
3 | WORKDIR /usr/src/app
4 | COPY package*.json ./
5 | RUN npm install -g
6 | COPY . .
7 | RUN npm run build
8 | EXPOSE 8080 3333 9090
9 | CMD [ "npm", "run", "dev:hot" ]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Open Source Labs 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 | # PodMD
2 |
3 | PodMD is a tool for developers utilizing and maintaining kubernetes clusters.\
4 | With PodMD, you may set a specific configuration of desired pod metrics\
5 | you wish to monitor and enable an automatic restart of designated pods based\
6 | on your specific needs.
7 |
8 | ## Getting Started
9 |
10 | In order to use PodMD, you need to deploy Prometheus on your cluster to\
11 | monitor pod metrics. You may also wish to install Grafana, but it isn't\
12 | necessary for PodMD to function.
13 |
14 | Additionally, it is _strongly_ recommended you utilize Helm for installing\
15 | the following tools. You can find instructions to install Helm below.
16 |
17 | # First: Initial Setup
18 |
19 | You can deploy the application either locally, to Minikube, or on the cloud, to AWS.\
20 | See below for a detailed walkthrough.
21 |
22 | ## Implementing Monitoring via Minikube
23 |
24 | If using Minikube, perform the following steps to get your Kubernetes cluster\
25 | running with Prometheus. Continue to last, optional step if you would like to\
26 | access visualizations with Grafana.
27 |
28 | 1. Ensure that you have the following installed to your computer:\
29 | [Docker](https://www.docker.com/)\
30 | [Minikube](https://minikube.sigs.k8s.io/docs/start/)\
31 | [Helm](https://v3-1-0.helm.sh/docs/intro/install/)\
32 | [Kubernetes Kubectl](https://kubernetes.io/docs/tasks/tools/)\
33 | [Node.js](https://nodejs.org/en)
34 | 2. If you already have a Minikube cluster in Docker that is no longer running\
35 | and you are trying to restart the tool, you must first delete the old cluster\
36 | by running the following command in your home directory:
37 |
38 | ```
39 | minikube delete
40 | ```
41 |
42 | 3. Make sure you have Docker running then start your cluster and install the\
43 | Prometheus-operator by running the following commands in your home directory:
44 |
45 | ```
46 | minikube start
47 | ```
48 |
49 | ```
50 | helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
51 | helm repo add stable https://charts.helm.sh/stable
52 | helm repo update
53 | ```
54 |
55 | ```
56 | helm install prometheus prometheus-community/kube-prometheus-stack
57 | ```
58 |
59 | 4. Once Prometheus pods and services are running on your cluster, which can take\
60 | a few minutes, run Prometheus on [PORT 9090](https://localhost:9090/) with the following command:
61 | ```
62 | kubectl port-forward prometheus-prometheus-kube-prometheus-prometheus-0 9090
63 | ```
64 | 5. After navigating to [PORT 9090](https://localhost:9090/) you may enter commands in the\
65 | Prometheus dashboard if you would like to test its functionality. The search\
66 | bar requires the use of PromQL to gather various metrics. You can read more\
67 | here: [Prometheus Documentation | Query Examples](https://prometheus.io/docs/prometheus/latest/querying/examples/)
68 | 6. Clone this PodMD repository to your computer.
69 | 7. Install dependencies by running the following command from your new, local repository:
70 | ```
71 | npm install
72 | ```
73 |
74 | ## Implementing Monitoring via AWS EKS
75 |
76 | 1. Ensure that you have the following installed to your computer:\
77 | [AWS Command Line Interface](https://aws.amazon.com/cli/)\
78 | [AWS EKS Command Line Interface (eksctl)](https://eksctl.io/installation/)\
79 | [Helm](https://v3-1-0.helm.sh/docs/intro/install/)\
80 | [Kubernetes Kubectl](https://kubernetes.io/docs/tasks/tools/)\
81 | [Node.js](https://nodejs.org/en)
82 | 2. Clone this PodMD repository to your computer and run the following command from the\
83 | resulting directory :
84 | ```
85 | aws configure
86 | ```
87 | [AWS User Guide](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html)
88 | 3. Create AWS EKS Clusters, example command is provided below with region set to us-west-1.\
89 | Clusters must be at least size medium to operate:
90 | ```
91 | eksctl create cluster --name=prometheus-3 --region=us-west-1 --version=1.31 --nodegroup-name=promnodes --node-type t2.medium --nodes 2
92 | ```
93 | 4. Once AWS Cluster is running, install the Prometheus-operator by running the following commands in your home directory:
94 | ```
95 | helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
96 | helm repo add stable https://charts.helm.sh/stable
97 | helm repo update
98 | ```
99 | 5. Deploy Prometheus on your cluster by running the following command in your home directory:
100 | ```
101 | helm install prometheus prometheus-community/kube-prometheus-stack
102 | ```
103 | 6. Once Prometheus pods and services are running on your cluster, which can take\
104 | a few minutes, run Prometheus on [PORT 9090](https://localhost:9090/) with the following command:
105 | ```
106 | kubectl port-forward prometheus-prometheus-kube-prometheus-prometheus-0 9090
107 | ```
108 | 7. After navigating to [PORT 9090](https://localhost:9090/) you may enter commands in the\
109 | Prometheus dashboard if you would like to test its functionality. The search\
110 | bar requires the use of PromQL to gather various metrics. You can read more\
111 | here: [Prometheus Documentation | Query Examples](https://prometheus.io/docs/prometheus/latest/querying/examples/)
112 | 8. Install dependencies by running the following command from your new, local repository:
113 | ```
114 | npm install
115 | ```
116 |
117 | # Next: Running Application
118 |
119 | You can run the application either in your browser or in a desktop application. See below for\
120 | detailed instructions on how to run the application:
121 |
122 | ## Running Application in Browser
123 |
124 | 1. Build the application by running the following command from your new, local repository:
125 | ```
126 | npm run build
127 | ```
128 | 2. Start the application by running the following command from your new, local repository:
129 | ```
130 | npm start
131 | ```
132 |
133 | ## Launch Desktop Application
134 |
135 | 1. Build and run Electron app by running the following command from your new, local\
136 | repository:
137 | ```
138 | npm run electron
139 | ```
140 |
141 | # Optional: Accessing Grafana Visualizations
142 |
143 | Should you also wish to run Grafana in your browser, this can by done by running the\
144 | following command from your home directory:
145 |
146 | ```
147 | kubectl port-forward deployments/prometheus-grafana 3000
148 | ```
149 |
150 | Navigate to [PORT 3000](https://localhost:3000/). Finally, you will need to login to access visualizations. The default\
151 | username is `admin` and the default password is `prom-operator`.
152 |
153 | PodMD
154 |
--------------------------------------------------------------------------------
/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | // __mocks__/fileMock.js
2 | module.exports = 'test-file-stub';
3 |
--------------------------------------------------------------------------------
/__tests__/configController.test.js:
--------------------------------------------------------------------------------
1 | const {
2 | config,
3 | configController,
4 | } = require('../server/controllers/configController');
5 | const { queryPrometheus } = require('../server/services/prometheusService');
6 |
7 |
8 | jest.mock('../server/services/prometheusService', () => ({
9 | queryPrometheus: jest.fn(),
10 | }));
11 |
12 | describe('configController.saveConfig', () => {
13 | let req, res, next;
14 |
15 | beforeEach(() => {
16 | req = {
17 | body: {
18 | memory: 75,
19 | memTimeFrame: 20,
20 | cpu: 85,
21 | cpuTimeFrame: 15,
22 | },
23 | };
24 | res = { locals: {} };
25 | next = jest.fn();
26 | queryPrometheus.mockClear();
27 | });
28 |
29 | test('should update config and call queryPrometheus with new queries', async () => {
30 | await configController.saveConfig(req, res, next);
31 |
32 | expect(config.cpu.threshold).toBe(85);
33 | expect(config.cpu.minutes).toBe(15);
34 | expect(config.memory.threshold).toBe(75);
35 | expect(config.memory.minutes).toBe(20);
36 |
37 | expect(queryPrometheus).toHaveBeenCalledWith(
38 | expect.stringContaining('[15m]')
39 | );
40 | expect(queryPrometheus).toHaveBeenCalledWith(
41 | expect.stringContaining('[20m]')
42 | );
43 |
44 | expect(res.locals.savedConfig).toEqual({
45 | cpu: { threshold: 85, minutes: 15 },
46 | memory: { threshold: 75, minutes: 20 },
47 | });
48 |
49 | expect(next).toHaveBeenCalled();
50 | });
51 |
52 | test('should call next with an error if an exception is thrown', async () => {
53 | const error = new Error('Test error');
54 | queryPrometheus.mockImplementationOnce(() => {
55 | throw error;
56 | });
57 |
58 | await configController.saveConfig(req, res, next);
59 |
60 | expect(next).toHaveBeenCalledWith(error);
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/__tests__/kubeController.test.js:
--------------------------------------------------------------------------------
1 | const deletePod = require('../server/controllers/kubeController');
2 | const k8s = require('@kubernetes/client-node');
3 |
4 | jest.mock('@kubernetes/client-node', () => {
5 | const CoreV1ApiMock = {
6 | deleteNamespacedPod: jest.fn(),
7 | };
8 |
9 | return {
10 | KubeConfig: jest.fn().mockImplementation(() => ({
11 | loadFromDefault: jest.fn(),
12 | makeApiClient: jest.fn().mockReturnValue(CoreV1ApiMock),
13 | })),
14 | CoreV1Api: jest.fn(() => CoreV1ApiMock),
15 | };
16 | });
17 |
18 | describe('deletePod', () => {
19 | let consoleSpy;
20 |
21 | beforeEach(() => {
22 | consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
23 | });
24 |
25 | afterEach(() => {
26 | consoleSpy.mockRestore();
27 | jest.clearAllMocks();
28 | });
29 |
30 | test('should call deleteNamespacedPod with correct arguments and log success on deletion', async () => {
31 | const k8sApi = new k8s.CoreV1Api();
32 | k8sApi.deleteNamespacedPod.mockResolvedValueOnce({});
33 |
34 | await deletePod('test-pod', 'default');
35 |
36 | expect(k8sApi.deleteNamespacedPod).toHaveBeenCalledWith(
37 | 'test-pod',
38 | 'default'
39 | );
40 | expect(consoleSpy).not.toHaveBeenCalled();
41 | });
42 |
43 | test('should log an error message when deletion fails', async () => {
44 | const error = new Error('Failed to delete pod');
45 | const k8sApi = new k8s.CoreV1Api();
46 | k8sApi.deleteNamespacedPod.mockRejectedValueOnce(error);
47 |
48 | await deletePod('test-pod', 'default');
49 |
50 | expect(k8sApi.deleteNamespacedPod).toHaveBeenCalledWith(
51 | 'test-pod',
52 | 'default'
53 | );
54 | expect(consoleSpy).toHaveBeenCalledWith(
55 | expect.stringContaining('Error deleting pod')
56 | );
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/__tests__/prometheusController.test.js:
--------------------------------------------------------------------------------
1 | const {
2 | prometheusController,
3 | restartedPods,
4 | checkRestart,
5 | } = require('../server/controllers/prometheusController');
6 | const { config } = require('../server/controllers/configController');
7 | const { queryPrometheus } = require('../server/services/prometheusService');
8 | const deletePod = require('../server/controllers/kubeController');
9 |
10 | jest.mock('../server/services/prometheusService', () => ({
11 | queryPrometheus: jest.fn(),
12 | runDemo: false,
13 | }));
14 |
15 | jest.mock('../server/controllers/kubeController', () => jest.fn());
16 |
17 | describe('Prometheus Controller', () => {
18 | afterEach(() => {
19 | jest.clearAllMocks();
20 | restartedPods.length = 0;
21 | });
22 |
23 | describe('fetchGraphData', () => {
24 | it('should query CPU data when cpuGraphMinutes is provided', async () => {
25 | const req = { query: { cpuGraphMinutes: '15' } };
26 | const res = { locals: {} };
27 | const next = jest.fn();
28 | const mockCpuData = {
29 | status: 'success',
30 | data: { result: [{ value: [null, 70] }] },
31 | };
32 |
33 | queryPrometheus.mockResolvedValueOnce(mockCpuData);
34 |
35 | await prometheusController.fetchGraphData(req, res, next);
36 |
37 | expect(queryPrometheus).toHaveBeenCalledWith(
38 | expect.stringContaining('[15m]'),
39 | config.cpu.threshold
40 | );
41 | expect(res.locals.data).toEqual({ cpuData: mockCpuData });
42 | expect(next).toHaveBeenCalled();
43 | });
44 |
45 | it('should query Memory data when memoryGraphMinutes is provided', async () => {
46 | const req = { query: { memoryGraphMinutes: '20' } };
47 | const res = { locals: {} };
48 | const next = jest.fn();
49 | const mockMemData = {
50 | status: 'success',
51 | data: { result: [{ value: [null, 60] }] },
52 | };
53 |
54 | queryPrometheus.mockResolvedValueOnce(mockMemData);
55 |
56 | await prometheusController.fetchGraphData(req, res, next);
57 |
58 | expect(queryPrometheus).toHaveBeenCalledWith(
59 | expect.stringContaining('[20m]'),
60 | config.memory.threshold
61 | );
62 | expect(res.locals.data).toEqual({ memData: mockMemData });
63 | expect(next).toHaveBeenCalled();
64 | });
65 |
66 | it('should call next with an error if queryPrometheus fails', async () => {
67 | const req = { query: { cpuGraphMinutes: '15' } };
68 | const res = { locals: {} };
69 | const next = jest.fn();
70 | const error = new Error('Prometheus query failed');
71 |
72 | queryPrometheus.mockRejectedValueOnce(error);
73 |
74 | await prometheusController.fetchGraphData(req, res, next);
75 |
76 | expect(next).toHaveBeenCalledWith(error);
77 | });
78 | });
79 |
80 | describe('checkRestart', () => {
81 | const mockPodData = {
82 | status: 'success',
83 | data: {
84 | result: [
85 | {
86 | metric: { pod: 'test-pod', namespace: 'default' },
87 | value: [null, '85'],
88 | },
89 | ],
90 | },
91 | };
92 |
93 | it('should add pod to restartedPods and call deletePod when threshold is exceeded', async () => {
94 | queryPrometheus.mockResolvedValueOnce(mockPodData);
95 | config.cpu.threshold = 80;
96 |
97 | await checkRestart(config.cpu);
98 |
99 | expect(restartedPods.length).toBe(1);
100 | expect(restartedPods[0]).toMatchObject({
101 | podName: 'test-pod',
102 | namespace: 'default',
103 | label: config.cpu.label,
104 | value: '85',
105 | threshold: 80,
106 | });
107 | expect(deletePod).toHaveBeenCalledWith('test-pod', 'default');
108 | });
109 |
110 | it('should not add pod to restartedPods if threshold is not exceeded', async () => {
111 | const lowPodData = {
112 | ...mockPodData,
113 | data: {
114 | result: [
115 | {
116 | metric: { pod: 'low-pod', namespace: 'default' },
117 | value: [null, '75'],
118 | },
119 | ],
120 | },
121 | };
122 | queryPrometheus.mockResolvedValueOnce(lowPodData);
123 | config.cpu.threshold = 80;
124 |
125 | await checkRestart(config.cpu);
126 |
127 | expect(restartedPods.length).toBe(0);
128 | expect(deletePod).not.toHaveBeenCalled();
129 | });
130 | });
131 | });
132 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@babel/preset-env', '@babel/preset-react'],
3 | plugins: ['@babel/plugin-transform-modules-commonjs'],
4 | };
5 |
--------------------------------------------------------------------------------
/bundle.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/PodMD/56a0593a5c73316d4624976519606d64f8627829/bundle.js
--------------------------------------------------------------------------------
/client/assets/Aldrich/Aldrich-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/PodMD/56a0593a5c73316d4624976519606d64f8627829/client/assets/Aldrich/Aldrich-Regular.ttf
--------------------------------------------------------------------------------
/client/assets/Aldrich/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011, Matthew Desmond (http://www.madtype.com | mattdesmond@gmail.com),with Reserved Font Name Aldrich.
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | https://openfontlicense.org
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/client/assets/halfLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/PodMD/56a0593a5c73316d4624976519606d64f8627829/client/assets/halfLogo.png
--------------------------------------------------------------------------------
/client/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/PodMD/56a0593a5c73316d4624976519606d64f8627829/client/assets/logo.png
--------------------------------------------------------------------------------
/client/assets/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/PodMD/56a0593a5c73316d4624976519606d64f8627829/client/assets/logo2.png
--------------------------------------------------------------------------------
/client/assets/logoName.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/PodMD/56a0593a5c73316d4624976519606d64f8627829/client/assets/logoName.png
--------------------------------------------------------------------------------
/client/assets/slogan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/PodMD/56a0593a5c73316d4624976519606d64f8627829/client/assets/slogan.png
--------------------------------------------------------------------------------
/client/components/Graph.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef } from 'react';
2 | import { Chart, registerables } from 'chart.js';
3 | import { Typography } from '@mui/material';
4 |
5 | Chart.register(...registerables);
6 |
7 | const Graph = ({
8 | title,
9 | cpuGraphMinutes,
10 | memoryGraphMinutes,
11 | setCpuGraphMinutes,
12 | setMemoryGraphMinutes,
13 | data,
14 | }) => {
15 | const [graphDisplay, setGraphDisplay] = useState(null);
16 | const [graphTitleDisplay, setGraphTitleDisplay] = useState('');
17 |
18 | const chartRef = useRef(null);
19 |
20 | const handleSelectDisplay = (mins) => {
21 |
22 | if (title === 'CPU Usage') {
23 | setCpuGraphMinutes(mins);
24 | } else if (title === 'Memory Usage') {
25 | setMemoryGraphMinutes(mins);
26 | }
27 | };
28 |
29 | useEffect(() => {
30 | if (!data) {
31 | setGraphTitleDisplay('No Data Available');
32 | return;
33 | }
34 |
35 | const combinedData = data.map((item, index) => ({
36 | pod: item.metric.pod,
37 | usage: parseFloat(item.value[1]),
38 | }));
39 | const sortedData = combinedData.sort((a, b) => a.pod.localeCompare(b.pod));
40 |
41 | const labels = sortedData.map((item) => item.pod);
42 | const cpuUsages = sortedData.map((item) => item.usage);
43 | const barColors = cpuUsages.map((value) => {
44 | if (value >= 100) return 'rgba(222, 55, 27, 0.4)';
45 | else if (value >= 75) return 'rgba(216,190,31,0.4)';
46 | else return 'rgba(84,171,180,0.4)';
47 | });
48 | const borderColors = cpuUsages.map((value) => {
49 | if (value >= 100) return 'rgba(222, 55, 27, 1.0)';
50 | else if (value >= 75) return 'rgba(216,190,31,1.0)';
51 | else return 'rgba(84,171,180,1.0)';
52 | });
53 |
54 | if (graphDisplay) {
55 | graphDisplay.destroy();
56 | }
57 |
58 | const newGraphDisplay = new Chart(chartRef.current, {
59 | type: 'bar',
60 | data: {
61 | labels: labels,
62 | datasets: [
63 | {
64 | label: title,
65 | data: cpuUsages,
66 | backgroundColor: barColors,
67 | borderColor: borderColors,
68 | borderWidth: 1,
69 | },
70 | ],
71 | },
72 | options: {
73 | scales: {
74 | x: {
75 | ticks: {
76 | display: false,
77 | },
78 | },
79 | y: {
80 | beginAtZero: true,
81 | min: 0,
82 | max: 100,
83 | ticks: {
84 | stepSize: 10,
85 | callback: function (value) {
86 | return value + '%';
87 | },
88 | },
89 | },
90 | },
91 | plugins: {
92 | tooltip: {
93 | callbacks: {
94 | label: function (context) {
95 | const roundedValue = Math.round(context.raw);
96 | return `${context.dataset.label}: ${roundedValue}%`;
97 | },
98 | },
99 | },
100 | },
101 | },
102 | });
103 |
104 | setGraphDisplay(newGraphDisplay);
105 | }, [data, title]);
106 |
107 | return (
108 |