├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── __mocks__
├── fileMock.js
└── styleMock.js
├── __tests__
├── react-testing.jsx
└── supertest.js
├── build
├── 1ec0ec5a975db103093a1c3731fbea56.svg
├── 352bfcc759fedc27fafc6761c5d12463.svg
├── 6a12cd2f026803517d1e10685d1283e9.svg
├── 6eeb75c8b4bdf45e996a.ico
├── 8fe4af813952d3d8c7285fde6e71c874.svg
├── bundle.js
├── client
│ ├── App.js
│ ├── Pages
│ │ ├── Alerts.js
│ │ ├── CustomAlerts.js
│ │ ├── Grafana
│ │ │ ├── Cluster.js
│ │ │ ├── ClusterUseMethod.js
│ │ │ ├── CoreDNS.js
│ │ │ ├── Kubelet.js
│ │ │ ├── NodeUseMethod.js
│ │ │ └── Nodes.js
│ │ ├── Overview.js
│ │ ├── PromQuery.js
│ │ └── Setup.js
│ ├── components
│ │ ├── SetupButtons.js
│ │ ├── dashboard
│ │ │ ├── Banner.js
│ │ │ ├── Dashboard.js
│ │ │ └── Navbar.js
│ │ └── visualizer
│ │ │ └── Visualizer.js
│ └── index.js
├── e39b74c0d5a646510b4538fc590ba30c.png
├── edc8741c1a30665462cc7aba4b513bc8.svg
├── index.html
├── output.css
└── server
│ ├── controllers
│ ├── alertController.js
│ ├── alerts
│ │ ├── mergefile.js
│ │ └── replacefile.js
│ ├── apiController.js
│ ├── clusterController.js
│ └── setupController.js
│ ├── routes
│ ├── alerts.js
│ ├── cluster.js
│ ├── grafana.js
│ └── setup.js
│ ├── schema
│ └── schema.js
│ └── server.js
├── client
├── App.tsx
├── Pages
│ ├── Alerts.tsx
│ ├── CustomAlerts.tsx
│ ├── Grafana
│ │ ├── Cluster.tsx
│ │ ├── ClusterUseMethod.tsx
│ │ ├── CoreDNS.tsx
│ │ ├── Kubelet.tsx
│ │ ├── NodeUseMethod.tsx
│ │ └── Nodes.tsx
│ ├── Overview.tsx
│ ├── Particle.tsx
│ ├── PromQuery.tsx
│ └── Setup.tsx
├── assets
│ └── favicon.ico
├── components
│ ├── SetupButtons.tsx
│ ├── dashboard
│ │ ├── Banner.tsx
│ │ ├── Dashboard.tsx
│ │ └── Navbar.tsx
│ └── visualizer
│ │ ├── Visualizer.tsx
│ │ └── icons
│ │ ├── control-plane-icon.svg
│ │ ├── deployment-icon.svg
│ │ ├── namespace-icon.svg
│ │ ├── node-icon.svg
│ │ ├── pod-icon.svg
│ │ └── service-icon.svg
├── index.html
├── index.tsx
└── styles
│ ├── Cluster.gif
│ ├── index.css
│ ├── logo-color-transformed.png
│ └── logo-color.png
├── custom.d.ts
├── cypress.config.ts
├── cypress
├── .eslintrc.json
├── e2e
│ └── spec.cy.ts
├── fixtures
│ └── example.json
└── support
│ ├── commands.ts
│ └── e2e.ts
├── iframe.html
├── index.html
├── jest.config.js
├── launch-website
├── assets
│ ├── favicon.ico
│ └── img
│ │ ├── bg-masthead.jpg
│ │ ├── jordy.jpg
│ │ ├── kevin.png
│ │ ├── logo-color.png
│ │ ├── logo-white.png
│ │ ├── logo-white.svg
│ │ ├── mushrath.png
│ │ ├── portfolio
│ │ ├── fullsize
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ ├── 4.png
│ │ │ ├── 5.png
│ │ │ └── 6.png
│ │ └── thumbnails
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ ├── 4.png
│ │ │ ├── 5.png
│ │ │ └── 6.png
│ │ └── sheng.png
├── css
│ └── styles.css
├── index.html
└── js
│ └── scripts.js
├── package-lock.json
├── package.json
├── prometheus-grafana.yaml
├── readme_assets
├── alert-manager.png
├── custom-alert.gif
├── dashboards.png
├── k8sapi.png
├── linkedin.png
├── models
│ ├── jordy.jpeg
│ ├── kevin.png
│ ├── mushrath.png
│ └── sheng.jpg
├── nodes.png
├── prom.gif
└── visualizer.gif
├── server
├── controllers
│ ├── alertController.ts
│ ├── alerts
│ │ ├── HighCPUUsage-template.yaml
│ │ ├── HighCPUUsage.yaml
│ │ ├── HighMemoryUsage-template.yaml
│ │ ├── HighMemoryUsage.yaml
│ │ ├── KubeAPIDown.yaml
│ │ ├── KubeNodeDown-template.yaml
│ │ ├── KubeNodeDown.yaml
│ │ ├── alert-rules-template.yaml
│ │ ├── alert-rules.yaml
│ │ ├── mergefile.js
│ │ └── replacefile.js
│ ├── apiController.ts
│ ├── clusterController.ts
│ └── setupController.ts
├── redis
│ └── redis.js
├── routes
│ ├── alerts.ts
│ ├── cluster.ts
│ ├── grafana.ts
│ └── setup.ts
└── server.ts
├── tsconfig.json
└── webpack.config.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | jest: true,
6 | },
7 | extends: ['plugin:react/recommended', 'airbnb'],
8 | overrides: [],
9 | parserOptions: {
10 | ecmaVersion: 'latest',
11 | sourceType: 'module',
12 | },
13 | plugins: ['react'],
14 | rules: {},
15 | };
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | .vscode/
107 |
108 | dump.rdb
--------------------------------------------------------------------------------
/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | module.exports = 'test-file-stub';
2 |
--------------------------------------------------------------------------------
/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/__tests__/react-testing.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | render, screen, fireEvent, waitFor,
4 | } from '@testing-library/react';
5 | import userEvent from '@testing-library/user-event';
6 | import '@testing-library/jest-dom';
7 | import { ProSidebarProvider } from 'react-pro-sidebar';
8 | import { BrowserRouter } from 'react-router-dom';
9 | import ResizeObserver from 'use-resize-observer';
10 | import fetch from 'whatwg-fetch';
11 | import Banner from '../client/components/dashboard/Banner.tsx';
12 | import Navbar from '../client/components/dashboard/Navbar.tsx';
13 | import Cluster from '../client/Pages/Grafana/Cluster.tsx';
14 | import Alerts from '../client/Pages/Alerts.tsx';
15 | import PromQuery from '../client/Pages/PromQuery.tsx';
16 | import CustomAlerts from '../client/Pages/CustomAlerts.tsx';
17 | import Dashboard from '../client/components/dashboard/Dashboard.tsx';
18 | import Overview from '../client/Pages/Overview.tsx';
19 | import App from '../client/App.tsx';
20 | // import fetch from 'node-fetch';
21 |
22 | // test state of banner to make sure title is rendered
23 | // test props of navbar after rendering dashboard to make sure apiKey is passed down
24 | // test to make sure title is changing when selecting navbar elements
25 | // test to make sure that clicking on menu items causes a new component to be rendered
26 | // maybe check to see if backend calls have been made after clicking setup buttons
27 | // test visualizer to make sure it is rendering a react-graph-vis graph
28 |
29 | test('loads and displays banner', async () => {
30 | render();
31 |
32 | await screen.findByRole('heading');
33 |
34 | // ASSERT
35 | expect(screen.getByRole('heading')).toHaveTextContent('Testing banner');
36 | });
37 |
38 | test('Initial page loads with heading and buttons', async () => {
39 | render(
40 |
41 |
42 | ,
43 | );
44 | await screen.findByRole('heading');
45 |
46 | expect(screen.getByRole('heading')).toHaveTextContent('Fancy Buttons');
47 |
48 | expect(screen.getByText('Setup Prometheus')).toBeInTheDocument();
49 | expect(screen.getByText('Setup Grafana')).toBeInTheDocument();
50 | expect(screen.getByText('Start Port Forwarding')).toBeInTheDocument();
51 | expect(screen.getByText('Go to dashboard')).toBeInTheDocument();
52 | });
53 |
54 | // test('user is redirected to dashboard when Go-to-dashboard is clicked', async () => {
55 | // await render(
56 | //
57 | //
58 | // ,
59 | // );
60 |
61 | // // fireEvent.click(screen.getByText('Go to dashboard'));
62 | // expect(screen.getByText('Go to dashboard')).toHaveAttribute('href', '/Dashboard');
63 |
64 | // await fireEvent.click(screen.getByText('Go to dashboard'));
65 | // expect(window.location.pathname).toBe('/Dashboard');
66 | // });
67 |
68 | test('renders alerts iframe with correct src', async () => {
69 | await render();
70 | expect(screen.getByTitle('alert embed')).toHaveAttribute('src', 'http://localhost:9093');
71 | });
72 |
73 | test('renders prometheus iframe with correct src', async () => {
74 | await render();
75 | expect(screen.getByTitle('prom query embed')).toHaveAttribute('src', 'http://localhost:9090/graph?&hideGraph=1');
76 | });
77 |
78 | test('CustomAlerts renders initial radio buttons', () => {
79 | render();
80 | const memoryRadio = screen.getByLabelText(/memory usage/i);
81 | const cpuRadio = screen.getByLabelText(/cpu usage/i);
82 | const kubeRadio = screen.getByLabelText(/kube node down/i);
83 | expect(memoryRadio).toBeInTheDocument();
84 | expect(cpuRadio).toBeInTheDocument();
85 | expect(kubeRadio).toBeInTheDocument();
86 | });
87 |
88 | test('shows memory threshold form after selecting Memory radio button', () => {
89 | render();
90 | const memoryRadio = screen.getByLabelText(/memory usage/i);
91 |
92 | fireEvent.click(memoryRadio);
93 | fireEvent.click(screen.getByText('Next'));
94 | const memoryThresholdLabel = screen.getByLabelText(/alert after memory usage exceeds/i);
95 | const memoryThresholdInput = screen.getByRole('spinbutton');
96 | expect(memoryThresholdLabel).toBeInTheDocument();
97 | expect(memoryThresholdInput).toBeInTheDocument();
98 | });
99 |
100 | test('Grafana page renders iframe with correct src', async () => {
101 | const apiKey = '123456789';
102 | const uid = 'abcdefg';
103 | jest.spyOn(global, 'fetch').mockResolvedValueOnce({
104 | json: jest.fn().mockResolvedValueOnce(uid),
105 | });
106 | render();
107 | await waitFor(() => expect(screen.getByTitle('embed cluster')).toHaveAttribute(
108 | 'src',
109 | expect.stringContaining(
110 | `http://localhost:3001/d/${uid}/kubernetes-api-server?orgId=1&refresh=10s&from`,
111 | ),
112 | ));
113 | });
114 |
115 | jest.mock('../client/components/visualizer/Visualizer', () => function MockedTestVis() {
116 | return
;
117 | });
118 |
119 | test('Overview renders TestVis component inside a div with id visualizer', async () => {
120 | render();
121 | const visualizerDiv = screen.getByTestId('visualizer');
122 | expect(visualizerDiv).toContainElement(screen.getByTestId('mocked-test-vis'));
123 | });
124 | class ResizeObserver {
125 | observe() {}
126 | unobserve() {}
127 | }
128 | test('Navbar clicking a sub-menu expands and collapses it', async () => {
129 | window.ResizeObserver = ResizeObserver;
130 | const { getByText, queryByText } = render(
131 |
132 | {' '}
133 | );
134 | expect(queryByText('Use Method(Cluster)')).not.toBeVisible();
135 | fireEvent.click(getByText('Metrics'));
136 | expect(queryByText('Use Method(Cluster)')).toBeVisible();
137 | fireEvent.click(getByText('Metrics'));
138 | await waitFor(()=>expect(queryByText('Use Method(Cluster)')).not.toBeVisible());
139 | });
140 |
141 | test('collapses the sidebar when toggle button is clicked', async () => {
142 | window.ResizeObserver = ResizeObserver;
143 | const { getByTestId } = render(
144 |
145 | {' '}
146 | );
147 |
148 | expect(getByTestId('ps-sidebar-root-test-id')).not.toHaveClass('ps-collapsed');
149 |
150 | fireEvent.click(getByTestId('pro-sidebar'));
151 | expect(getByTestId('ps-sidebar-root-test-id')).toHaveClass('ps-collapsed');
152 | fireEvent.click(getByTestId('pro-sidebar'));
153 | expect(getByTestId('ps-sidebar-root-test-id')).not.toHaveClass('ps-collapsed');
154 | });
155 |
156 |
157 |
158 | // testing dashboard component renders correctly
159 | // describe('Dashboard component', () => {
160 | // it('renders Navbar with apiKey prop', async () => {
161 | // const apiKey = 'testApiKey';
162 |
163 | // // Create a mock global object with the fetch property
164 | // const mockGlobal = {
165 | // fetch: jest.fn().mockResolvedValue({
166 | // json: jest.fn().mockResolvedValue(apiKey),
167 | // }),
168 | // };
169 |
170 | // // Replace the global object with the mock object
171 | // const originalGlobal = global;
172 | // global = mockGlobal;
173 |
174 | // render();
175 |
176 | // // Wait for the fetch request to complete and update the state
177 | // const navbar = await screen.findByTestId('navbar');
178 | // expect(navbar).toHaveAttribute('apiKey', apiKey);
179 |
180 | // // Clean up the mock fetch request
181 | // global = originalGlobal;
182 | // });
183 | // });
184 |
185 | // test('navbar is present when Dashboard is rendered', async () => {
186 | // render();
187 |
188 | // const navBarElement = await screen.findByTestId('navbar');
189 |
190 | // // ASSERT
191 | // expect(navBarElement).toBeInTheDocument();
192 | // });
193 |
--------------------------------------------------------------------------------
/__tests__/supertest.js:
--------------------------------------------------------------------------------
1 | const request = require('supertest');
2 |
3 | const server = 'http://localhost:3000';
4 |
5 | xdescribe('Route integration', () => {
6 | xdescribe('/', () => {
7 | // describe('GET', () => {
8 | // // Note that we return the evaluation of `request` here! It evaluates to
9 | // // a promise, so Jest knows not to say this test passes until that
10 | // // promise resolves. See https://jestjs.io/docs/en/asynchronous
11 | // it('responds with 200 status and text/html content type', () =>
12 | // request(server)
13 | // .get('/')
14 | // .expect('Content-Type', /text\/html/)
15 | // .expect(200));
16 | // });
17 | });
18 | xdescribe('setup router', () => {
19 | describe('/promSetup', () => {
20 | it('responds with status 200', () =>
21 | request(server).get('/setup/promSetup').expect(200));
22 | });
23 | describe('/grafSetup', () => {
24 | it('responds with status 200', () =>
25 | request(server).get('/setup/grafSetup').expect(200));
26 | });
27 | describe('/forwardPorts', () => {
28 | it('responds with status 200', () =>
29 | request(server).get('/setup/forwardPorts').expect(200));
30 | });
31 | });
32 | xdescribe('/clusterdata', () => {
33 | describe('/clusterdata', () => {
34 | it('responds with object', () =>
35 | request(server)
36 | .get('/clusterdata')
37 | .expect(200)
38 | .expect((res) => {
39 | expect(res.body).toMatchObject({
40 | nodes: expect.any(Array),
41 | pods: expect.any(Array),
42 | namespaces: expect.any(Array),
43 | services: expect.any(Array),
44 | deployments: expect.any(Array),
45 | ingresses: expect.any(Array),
46 | });
47 | }));
48 | });
49 | });
50 |
51 | xdescribe('alertsRouter', () => {
52 | describe('/alerts', () => {
53 | it('should create an alert successfuly', () =>
54 | request(server)
55 | .post('/alerts')
56 | .send({ type: 'CPU', threshold: 80, name: 'HighCPUUsage' })
57 | .expect(200)
58 | .then((res) => {
59 | expect(res.text).toBe('Alert created successfully');
60 | }));
61 | });
62 | describe('/alerts', () => {
63 | it('should handle errors correctly', () => {
64 | const mockCreateAlert = jest.fn((req, res, next) =>
65 | next(new Error('An error occured'))
66 | );
67 | jest.doMock('../server/controllers/alertController', () => ({
68 | createAlert: mockCreateAlert,
69 | }));
70 | request(server)
71 | .post('/alerts')
72 | .send({
73 | type: 'CPU',
74 | threshold: 80,
75 | name: 'MyAlert',
76 | })
77 | .expect(500)
78 | .then((res) => {
79 | expect(res.text).toContain('An error occured');
80 | });
81 | jest.resetModules();
82 | });
83 | });
84 | });
85 | describe('grafana router', () => {
86 | xdescribe('/key GET', () => {
87 | it('should return an API key', () => {
88 | request(server)
89 | .get('/grafana/key')
90 | .expect((res) => {
91 | expect(typeof res.body.key).toBe('string');
92 | })
93 | .expect(200);
94 | });
95 | });
96 | describe('/Uid', () => {
97 | it('should return Uid', () => {
98 | request(server)
99 | .post('/grafana/uid')
100 | .send({
101 | key: 'eyJrIjoiaHFjejVnb0Vua0xFRWFwbjMzVHd1ZnZBWThPZjlDMnUiLCJuIjoia2NzYXIiLCJpZCI6MX0=',
102 | dashboard: 'Kubernetes / API server',
103 | })
104 | .expect(200)
105 | .then((res) => {
106 | // console.log(res.body);
107 | expect(typeof res.body).toBe('string');
108 | });
109 | });
110 | });
111 | });
112 | });
113 |
--------------------------------------------------------------------------------
/build/1ec0ec5a975db103093a1c3731fbea56.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
115 |
--------------------------------------------------------------------------------
/build/352bfcc759fedc27fafc6761c5d12463.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
97 |
--------------------------------------------------------------------------------
/build/6eeb75c8b4bdf45e996a.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/build/6eeb75c8b4bdf45e996a.ico
--------------------------------------------------------------------------------
/build/8fe4af813952d3d8c7285fde6e71c874.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
129 |
--------------------------------------------------------------------------------
/build/client/App.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const react_1 = __importDefault(require("react"));
7 | const react_router_dom_1 = require("react-router-dom");
8 | const Dashboard_1 = __importDefault(require("./components/dashboard/Dashboard"));
9 | const SetupButtons_1 = __importDefault(require("./components/SetupButtons"));
10 | // Custom Theme for Material UI
11 | function App() {
12 | return (react_1.default.createElement("div", null,
13 | react_1.default.createElement(react_router_dom_1.Routes, null,
14 | react_1.default.createElement(react_router_dom_1.Route, { path: "/Dashboard/*", element: react_1.default.createElement(Dashboard_1.default, null) }),
15 | react_1.default.createElement(react_router_dom_1.Route, { path: "/", element: react_1.default.createElement(SetupButtons_1.default, null) }),
16 | react_1.default.createElement(react_router_dom_1.Route, { path: "/Dashboard", element: react_1.default.createElement(react_router_dom_1.Navigate, { to: "Dashboard/Overview" }) }) // removed exact from path
17 | ,
18 | " // removed exact from path")));
19 | }
20 | exports.default = App;
21 |
--------------------------------------------------------------------------------
/build/client/Pages/Alerts.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const react_1 = __importDefault(require("react"));
7 | function Alerts() {
8 | return (react_1.default.createElement("div", { className: 'iframe' },
9 | react_1.default.createElement("iframe", { src: "http://localhost:9093", width: "100%", height: "100%", title: 'alert embed' })));
10 | }
11 | exports.default = Alerts;
12 |
--------------------------------------------------------------------------------
/build/client/Pages/CustomAlerts.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | Object.defineProperty(exports, "__esModule", { value: true });
26 | const react_1 = __importStar(require("react"));
27 | function CustomAlerts() {
28 | const [selectedAlertOption, setSelectedAlertOption] = (0, react_1.useState)('');
29 | const [submittedAlertOption, setSubmittedAlertOption] = (0, react_1.useState)('');
30 | const [selectedMemory, setSelectedMemory] = (0, react_1.useState)('');
31 | const [submittedMemory, setsubmittedMemory] = (0, react_1.useState)('');
32 | const [selectedCPU, setSelectedCPU] = (0, react_1.useState)('');
33 | const [submittedCPU, setSubmittedCPU] = (0, react_1.useState)('');
34 | const [alertName, setAlertName] = (0, react_1.useState)('');
35 | const handleTypeSubmit = (event) => {
36 | event.preventDefault();
37 | setSubmittedAlertOption(selectedAlertOption);
38 | };
39 | const handleRadioChange = (event) => {
40 | setSelectedAlertOption(event.target.value);
41 | };
42 | const handleMemoryChange = (event) => {
43 | setSelectedMemory(event.target.value);
44 | };
45 | const handleMemorySubmit = (event) => {
46 | event.preventDefault();
47 | setsubmittedMemory(selectedMemory);
48 | };
49 | const handleCPUChange = (event) => {
50 | setSelectedCPU(event.target.value);
51 | };
52 | const handleCPUSubmit = (event) => {
53 | event.preventDefault();
54 | setSubmittedCPU(selectedCPU);
55 | };
56 | const handleNameChange = (event) => {
57 | setAlertName(event.target.value);
58 | };
59 | const handleFormSubmit = (event) => {
60 | event.preventDefault();
61 | event.preventDefault();
62 | setSubmittedAlertOption(selectedAlertOption);
63 | const threshold = submittedAlertOption === 'Memory' ? submittedMemory : submittedCPU;
64 | const result = { name: alertName, type: submittedAlertOption, threshold };
65 | setsubmittedMemory('');
66 | setSelectedMemory('');
67 | setSubmittedCPU('');
68 | setSelectedCPU('');
69 | setAlertName('');
70 | setSelectedAlertOption('');
71 | setSubmittedAlertOption('');
72 | fetch('http://localhost:3000/alerts', {
73 | method: 'POST',
74 | headers: {
75 | 'Content-Type': 'application/json',
76 | },
77 | body: JSON.stringify(result),
78 | })
79 | .then((res) => res.json())
80 | .then((data) => {
81 | console.log(data);
82 | })
83 | .catch((error) => {
84 | console.error('Error:', error);
85 | });
86 | };
87 | return (react_1.default.createElement("div", null,
88 | react_1.default.createElement("iframe", { src: "https://giphy.com/embed/dWa2rUaiahx1FB3jor", width: "480", height: "480", frameBorder: "0", className: "giphy-embed", allowFullScreen: true }),
89 | submittedAlertOption === '' && (react_1.default.createElement("div", { className: "add-alert" },
90 | react_1.default.createElement("h3", null, "type of alerts"),
91 | react_1.default.createElement("form", { onSubmit: handleTypeSubmit },
92 | react_1.default.createElement("input", { type: "radio", id: "memory", name: "alertType", value: "Memory", onChange: handleRadioChange }),
93 | react_1.default.createElement("label", { htmlFor: "memory" }, "Memory Usage"),
94 | react_1.default.createElement("br", null),
95 | react_1.default.createElement("input", { type: "radio", id: "cpu", name: "alertType", value: "CPU", onChange: handleRadioChange }),
96 | react_1.default.createElement("label", { htmlFor: "cpu" }, "CPU Usage"),
97 | react_1.default.createElement("br", null),
98 | react_1.default.createElement("input", { type: "radio", id: "kube", name: "alertType", value: "Kube", onChange: handleRadioChange }),
99 | react_1.default.createElement("label", { htmlFor: "kube" }, "Kube Node Down"),
100 | react_1.default.createElement("br", null),
101 | react_1.default.createElement("input", { type: "submit", value: "Next" })))),
102 | submittedAlertOption === 'Memory' && submittedMemory === '' && (react_1.default.createElement("div", { className: "add-alert" },
103 | react_1.default.createElement("h3", null, "Memory Threshold"),
104 | react_1.default.createElement("form", { onSubmit: handleMemorySubmit },
105 | react_1.default.createElement("label", { htmlFor: "memorythreshold" }, "Alert after memory usage exceeds (in Gigabytes)"),
106 | react_1.default.createElement("input", { type: "number", id: "memorythreshold", name: "memorythreshold", onChange: handleMemoryChange }),
107 | react_1.default.createElement("br", null),
108 | react_1.default.createElement("input", { type: "submit", value: "Next" })))),
109 | (submittedMemory !== '' ||
110 | submittedCPU !== '' ||
111 | submittedAlertOption === 'Kube') && (react_1.default.createElement("div", { className: "add-alert" },
112 | react_1.default.createElement("h3", null, "Create Alert Name"),
113 | react_1.default.createElement("form", { onSubmit: handleFormSubmit },
114 | react_1.default.createElement("label", { htmlFor: "memorythreshold" }, "Enter alert name"),
115 | react_1.default.createElement("input", { type: "input", id: "alertname", name: "alertname", onChange: handleNameChange }),
116 | react_1.default.createElement("br", null),
117 | react_1.default.createElement("input", { type: "submit", value: "Create Alert" })))),
118 | submittedAlertOption === 'CPU' && submittedCPU === '' && (react_1.default.createElement("div", { className: "add-alert" },
119 | react_1.default.createElement("h3", null, "CPU Threshold"),
120 | react_1.default.createElement("form", { onSubmit: handleCPUSubmit },
121 | react_1.default.createElement("label", { htmlFor: "cputhreshold" }, "Alert after total CPU usage exceeds"),
122 | react_1.default.createElement("input", { type: "number", id: "cputhreshold", name: "cputhreshold", step: "0.1", onChange: handleCPUChange }),
123 | react_1.default.createElement("br", null),
124 | react_1.default.createElement("input", { type: "submit", value: "Next" }))))));
125 | }
126 | exports.default = CustomAlerts;
127 |
--------------------------------------------------------------------------------
/build/client/Pages/Grafana/Cluster.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | Object.defineProperty(exports, "__esModule", { value: true });
26 | const react_1 = __importStar(require("react"));
27 | function Cluster({ apiKey }) {
28 | const [uid, setUid] = (0, react_1.useState)(null);
29 | const now = new Date().getTime();
30 | const from = new Date(now - 4 * 60 * 60 * 1000).getTime();
31 | (0, react_1.useEffect)(() => {
32 | fetch('http://localhost:3000/grafana/uid', {
33 | method: 'POST',
34 | headers: {
35 | 'Content-Type': 'application/json',
36 | },
37 | body: JSON.stringify({
38 | key: apiKey,
39 | dashboard: 'Kubernetes / API server',
40 | }),
41 | })
42 | .then((res) => res.json())
43 | .then((data) => {
44 | console.log(data);
45 | setUid(data);
46 | });
47 | }, []);
48 | const url = `http://localhost:3001/d/${uid}/kubernetes-api-server?orgId=1&refresh=10s&from=${from}&to=${now}&kiosk=true&theme=light`;
49 | return (react_1.default.createElement("div", { className: "iframe" },
50 | react_1.default.createElement("iframe", { className: "frame", src: url, width: "100%", height: "100%", title: "embed cluster" })));
51 | }
52 | exports.default = Cluster;
53 | // http://localhost:3001/d/${uid}/kubernetes-api-server?orgId=1&refresh=10s&from=1677342274613&to=1677345874613&kiosk=true&theme=light
54 |
--------------------------------------------------------------------------------
/build/client/Pages/Grafana/ClusterUseMethod.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | Object.defineProperty(exports, "__esModule", { value: true });
26 | const react_1 = __importStar(require("react"));
27 | function ClusterUseMethod({ apiKey }) {
28 | const [uid, setUid] = (0, react_1.useState)(null);
29 | const now = new Date().getTime();
30 | const from = new Date(now - 4 * 60 * 60 * 1000).getTime();
31 | (0, react_1.useEffect)(() => {
32 | fetch('http://localhost:3000/grafana/uid', {
33 | method: 'POST',
34 | headers: {
35 | 'Content-Type': 'application/json',
36 | },
37 | body: JSON.stringify({
38 | key: apiKey,
39 | dashboard: 'Node Exporter / USE Method / Cluster',
40 | }),
41 | })
42 | .then((res) => res.json())
43 | .then((data) => {
44 | setUid(data);
45 | });
46 | }, [apiKey]);
47 | const url = `http://localhost:3001/d/${uid}/node-exporter-use-method-cluster?orgId=1&refresh=30s&from=${from}&to=${now}&kiosk=true&theme=light`;
48 | return (react_1.default.createElement("div", { className: "iframe" },
49 | react_1.default.createElement("iframe", { src: url, width: "100%", height: "100%" })));
50 | }
51 | exports.default = ClusterUseMethod;
52 |
--------------------------------------------------------------------------------
/build/client/Pages/Grafana/CoreDNS.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | Object.defineProperty(exports, "__esModule", { value: true });
26 | const react_1 = __importStar(require("react"));
27 | function CoreDNS({ apiKey }) {
28 | const [uid, setUid] = (0, react_1.useState)(null);
29 | const now = new Date().getTime();
30 | const from = new Date(now - 4 * 60 * 60 * 1000).getTime();
31 | (0, react_1.useEffect)(() => {
32 | fetch('http://localhost:3000/grafana/uid', {
33 | method: 'POST',
34 | headers: {
35 | 'Content-Type': 'application/json',
36 | },
37 | body: JSON.stringify({
38 | key: apiKey,
39 | dashboard: 'CoreDNS',
40 | }),
41 | })
42 | .then((res) => res.json())
43 | .then((data) => {
44 | setUid(data);
45 | });
46 | }, [apiKey]);
47 | const url = `http://localhost:3001/d/${uid}/coredns?orgId=1&refresh=10s&from=${from}&to=${now}&kiosk=true&theme=light`;
48 | return (react_1.default.createElement("div", { className: "iframe" },
49 | react_1.default.createElement("iframe", { src: url, width: "100%", height: "100%" })));
50 | }
51 | exports.default = CoreDNS;
52 |
--------------------------------------------------------------------------------
/build/client/Pages/Grafana/Kubelet.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | Object.defineProperty(exports, "__esModule", { value: true });
26 | const react_1 = __importStar(require("react"));
27 | function Kubelet({ apiKey }) {
28 | const [uid, setUid] = (0, react_1.useState)(null);
29 | const now = new Date().getTime();
30 | const from = new Date(now - 4 * 60 * 60 * 1000).getTime();
31 | (0, react_1.useEffect)(() => {
32 | fetch('http://localhost:3000/grafana/uid', {
33 | method: 'POST',
34 | headers: {
35 | 'Content-Type': 'application/json',
36 | },
37 | body: JSON.stringify({
38 | key: apiKey,
39 | dashboard: 'Kubernetes / Kubelet',
40 | }),
41 | })
42 | .then((res) => res.json())
43 | .then((data) => {
44 | setUid(data);
45 | });
46 | }, [apiKey]);
47 | const url = `http://localhost:3001/d/${uid}/kubernetes-kubelet?orgId=1&refresh=10s&from=${from}&to=${now}&kiosk=true&theme=light`;
48 | return (react_1.default.createElement("div", { className: "iframe" },
49 | react_1.default.createElement("iframe", { src: url, width: "100%", height: "100%" })));
50 | }
51 | exports.default = Kubelet;
52 |
--------------------------------------------------------------------------------
/build/client/Pages/Grafana/NodeUseMethod.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | Object.defineProperty(exports, "__esModule", { value: true });
26 | const react_1 = __importStar(require("react"));
27 | function NodeUseMethod({ apiKey }) {
28 | const [uid, setUid] = (0, react_1.useState)(null);
29 | const now = new Date().getTime();
30 | const from = new Date(now - 4 * 60 * 60 * 1000).getTime();
31 | (0, react_1.useEffect)(() => {
32 | fetch('http://localhost:3000/grafana/uid', {
33 | method: 'POST',
34 | headers: {
35 | 'Content-Type': 'application/json',
36 | },
37 | body: JSON.stringify({
38 | key: apiKey,
39 | dashboard: 'Node Exporter / USE Method / Node',
40 | }),
41 | })
42 | .then((res) => res.json())
43 | .then((data) => {
44 | setUid(data);
45 | });
46 | }, [apiKey]);
47 | const url = `http://localhost:3001/d/${uid}/node-exporter-use-method-node?orgId=1&refresh=30s&from=${from}&to=${now}&kiosk=true&theme=light`;
48 | return (react_1.default.createElement("div", { className: "iframe" },
49 | react_1.default.createElement("iframe", { src: url, width: "100%", height: "100%" })));
50 | }
51 | exports.default = NodeUseMethod;
52 |
--------------------------------------------------------------------------------
/build/client/Pages/Grafana/Nodes.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | Object.defineProperty(exports, "__esModule", { value: true });
26 | const react_1 = __importStar(require("react"));
27 | function Nodes({ apiKey }) {
28 | const [uid, setUid] = (0, react_1.useState)(null);
29 | const now = new Date().getTime();
30 | const from = new Date(now - 4 * 60 * 60 * 1000).getTime();
31 | (0, react_1.useEffect)(() => {
32 | fetch('http://localhost:3000/grafana/uid', {
33 | method: 'POST',
34 | headers: {
35 | 'Content-Type': 'application/json',
36 | },
37 | body: JSON.stringify({
38 | key: apiKey,
39 | dashboard: 'Node Exporter / Nodes',
40 | }),
41 | })
42 | .then((res) => res.json())
43 | .then((data) => {
44 | setUid(data);
45 | });
46 | }, [apiKey]);
47 | const url = `http://localhost:3001/d/${uid}/node-exporter-nodes?orgId=1&refresh=30s&from=${from}&to=${now}&kiosk=true&theme=light`;
48 | return (react_1.default.createElement("div", { className: "iframe" },
49 | react_1.default.createElement("iframe", { src: url, width: "100%", height: "100%" })));
50 | }
51 | exports.default = Nodes;
52 |
--------------------------------------------------------------------------------
/build/client/Pages/Overview.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const react_1 = __importDefault(require("react"));
7 | const Visualizer_1 = __importDefault(require("../components/visualizer/Visualizer"));
8 | function Overview() {
9 | return (react_1.default.createElement("div", { id: "visualizer", "data-testid": "visualizer" },
10 | react_1.default.createElement(Visualizer_1.default, { "data-testid": 'test-vis' })));
11 | }
12 | exports.default = Overview;
13 |
--------------------------------------------------------------------------------
/build/client/Pages/PromQuery.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const react_1 = __importDefault(require("react"));
7 | function PromQuery() {
8 | return (react_1.default.createElement("div", { className: 'iframe' },
9 | react_1.default.createElement("iframe", { src: "http://localhost:9090/graph?&hideGraph=1", title: 'prom query embed', width: "100%", height: "100%" })));
10 | }
11 | exports.default = PromQuery;
12 |
--------------------------------------------------------------------------------
/build/client/Pages/Setup.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const react_1 = __importDefault(require("react"));
7 | const SetupButtons_1 = __importDefault(require("../components/SetupButtons"));
8 | function SetupPage() {
9 | return (react_1.default.createElement("div", null,
10 | react_1.default.createElement(SetupButtons_1.default, null)));
11 | }
12 | exports.default = SetupPage;
13 |
--------------------------------------------------------------------------------
/build/client/components/SetupButtons.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const react_1 = __importDefault(require("react"));
7 | const react_router_dom_1 = require("react-router-dom");
8 | function Setup() {
9 | return (react_1.default.createElement("div", null,
10 | react_1.default.createElement("h2", null, "Fancy Buttons"),
11 | react_1.default.createElement("button", { onClick: () => fetch('http://localhost:3000/setup/promSetup'), type: "button" }, "Setup Prometheus"),
12 | react_1.default.createElement("button", { onClick: () => fetch('http://localhost:3000/setup/grafSetup'), type: "button" }, "Setup Grafana"),
13 | react_1.default.createElement("button", { onClick: () => fetch('http://localhost:3000/setup/forwardPorts'), type: "button" }, "Start Port Forwarding"),
14 | react_1.default.createElement("button", { type: "button" },
15 | react_1.default.createElement(react_router_dom_1.Link, { to: "/Dashboard" }, "Go to dashboard"))));
16 | }
17 | exports.default = Setup;
18 |
--------------------------------------------------------------------------------
/build/client/components/dashboard/Banner.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const react_1 = __importDefault(require("react"));
7 | function Banner(props) {
8 | const { title } = props;
9 | return (react_1.default.createElement("div", { id: "Banner" },
10 | react_1.default.createElement("h2", null, title)));
11 | }
12 | exports.default = Banner;
13 |
--------------------------------------------------------------------------------
/build/client/components/dashboard/Dashboard.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | var __importDefault = (this && this.__importDefault) || function (mod) {
26 | return (mod && mod.__esModule) ? mod : { "default": mod };
27 | };
28 | Object.defineProperty(exports, "__esModule", { value: true });
29 | const react_1 = __importStar(require("react"));
30 | const Navbar_1 = __importDefault(require("./Navbar"));
31 | const react_pro_sidebar_1 = require("react-pro-sidebar");
32 | function Dashboard() {
33 | const [api, setApi] = (0, react_1.useState)('');
34 | (0, react_1.useEffect)(() => {
35 | fetch('http://localhost:3000/grafana/key', {
36 | method: 'GET',
37 | })
38 | .then((res) => res.json())
39 | .then((data) => {
40 | console.log('key', data);
41 | setApi(data);
42 | })
43 | .catch((error) => {
44 | console.log('error when fetching api key', error);
45 | });
46 | }, []);
47 | return (react_1.default.createElement("div", { className: "dashboard" },
48 | react_1.default.createElement(react_pro_sidebar_1.ProSidebarProvider, null,
49 | react_1.default.createElement(Navbar_1.default, { apiKey: api, "data-testid": "navbar" }))));
50 | }
51 | exports.default = Dashboard;
52 |
--------------------------------------------------------------------------------
/build/client/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const react_1 = __importDefault(require("react"));
7 | const client_1 = require("react-dom/client");
8 | const react_router_dom_1 = require("react-router-dom");
9 | const App_1 = __importDefault(require("./App"));
10 | require("./index.css");
11 | const root = (0, client_1.createRoot)(document.getElementById('mainBody'));
12 | root.render(react_1.default.createElement(react_router_dom_1.BrowserRouter, null,
13 | react_1.default.createElement(App_1.default, null)));
14 |
--------------------------------------------------------------------------------
/build/e39b74c0d5a646510b4538fc590ba30c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/build/e39b74c0d5a646510b4538fc590ba30c.png
--------------------------------------------------------------------------------
/build/edc8741c1a30665462cc7aba4b513bc8.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
104 |
--------------------------------------------------------------------------------
/build/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | kr8os
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build/output.css:
--------------------------------------------------------------------------------
1 | /* #0bc1d1
2 | #306ce6 */
3 |
4 | :root {
5 | --k8blue:#306ce6;
6 | --k8teal: #0bc1d1;
7 | }
8 |
9 | body {
10 | margin: 0;
11 | font-family: open sans,-apple-system,BlinkMacSystemFont,segoe ui,Roboto,helvetica neue,Arial,sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol;
12 | /* font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
13 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
14 | sans-serif; */
15 | -webkit-font-smoothing: antialiased;
16 | -moz-osx-font-smoothing: grayscale;
17 | /* additions here*/
18 | height: 100vh;
19 | background: var(--k8blue);
20 | }
21 |
22 | code {
23 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
24 | monospace;
25 | }
26 |
27 | #Banner {
28 | height: 100px;
29 | display: flex;
30 | flex-wrap: wrap;
31 | justify-content: center;
32 | align-items: center;
33 | color: white;
34 | }
35 |
36 | #Banner h2 {
37 | margin-left:25px;
38 | font-weight: 700;
39 | font-size: 2rem;
40 | }
41 |
42 | .buttonContainer {
43 | display: flex;
44 | gap: 25px;
45 | margin-right: 25px;
46 | }
47 |
48 | .navbutton {
49 | text-decoration: none;
50 | }
51 |
52 | .iframe {
53 | width:100%;
54 | height:100%;
55 | }
56 |
57 | .frame {
58 | margin:auto;
59 | }
60 |
61 | .link {
62 | text-decoration: none;
63 | }
64 |
65 | .link:visited {
66 | color: inherit;
67 | }
68 |
69 | .page {
70 | display: flex;
71 | flex-direction: column;
72 | align-items: center;
73 | /* justify-content: center; */
74 | /* height: 100vh; */
75 | height: 100%;
76 | width: 100%;
77 | background-color: var(--k8blue);
78 | }
79 |
80 | #logo {
81 | margin-top:25px;
82 | margin-bottom: 25px;
83 | }
84 |
85 | #visualizer{
86 | height:calc((100vh - 100px) - 2.5%);
87 | /* flex: 1; */
88 | width: 95%;
89 | display: flex;
90 | justify-content: center;
91 | /* align-self: center; */
92 | border-style: solid;
93 | border-width: 3px;
94 | border-color: #306ce6;
95 | border-radius: 30px;
96 | box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
97 | }
98 |
99 | /* canvas {
100 |
101 | }
102 |
103 | #viz-container {
104 | z-index: 200000000000;
105 | } */
106 |
107 | .popup {
108 | /* top: 50%;
109 | left: 50%;
110 | transform: translate(-50%, -50%);
111 | z-index: 9999; */
112 | background-color: var(--k8teal);
113 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
114 | border-radius: 5px;
115 | padding: 20px;
116 | color: #fff;
117 | }
118 |
119 | .popup h2 {
120 | font-size: 1.5rem;
121 | font-weight: bold;
122 | margin-top: 0;
123 | }
124 |
125 | .popup p {
126 | font-size: 1.2rem;
127 | margin-bottom: 20px;
128 | }
129 |
130 | #visualizer {
131 | background-color: white;
132 | }
133 |
134 | div.vis-tooltip {
135 | position: absolute;
136 | }
--------------------------------------------------------------------------------
/build/server/controllers/alertController.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 | return new (P || (P = Promise))(function (resolve, reject) {
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
9 | });
10 | };
11 | var __importDefault = (this && this.__importDefault) || function (mod) {
12 | return (mod && mod.__esModule) ? mod : { "default": mod };
13 | };
14 | Object.defineProperty(exports, "__esModule", { value: true });
15 | const fs_1 = __importDefault(require("fs"));
16 | const child_process_1 = require("child_process");
17 | const path_1 = __importDefault(require("path"));
18 | // import { PathOrFileDescriptor } from 'fs-extra';
19 | const createAlert = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
20 | const { type, threshold, name } = req.body;
21 | let filePath;
22 | let oldText;
23 | let newText;
24 | let oldName;
25 | let command;
26 | switch (type) {
27 | case 'CPU':
28 | filePath = path_1.default.join(__dirname, 'alerts/HighCPUUsage.yaml');
29 | oldText = /1\b/g;
30 | newText = `sum(rate(container_cpu_usage_seconds_total{namespace="default"}[5m])) by (pod_name) > ${threshold}`;
31 | oldName = 'HighCPUUsage';
32 | command =
33 | 'helm upgrade --reuse-values -f server/controllers/alerts/HighCPUUsage.yaml prometheus prometheus-community/kube-prometheus-stack -n default';
34 | break;
35 | case 'Memory':
36 | filePath = path_1.default.join(__dirname, 'alerts/HighMemoryUsage.yaml');
37 | oldText =
38 | 'sum(container_memory_working_set_bytes{namespace="default"}) by (pod_name) > 1e+09';
39 | newText = `sum(container_memory_working_set_bytes{namespace="default"}) by (pod_name) > ${threshold}e+09`;
40 | oldName = 'HighMemoryUsage';
41 | command =
42 | 'helm upgrade --reuse-values -f server/controllers/alerts/HighMemoryUsage.yaml prometheus prometheus-community/kube-prometheus-stack -n default';
43 | break;
44 | default:
45 | filePath = path_1.default.join(__dirname, 'alerts/KubeNodeDown.yaml');
46 | oldText = '';
47 | newText = '';
48 | oldName = 'KubeNodeDown';
49 | command =
50 | 'helm upgrade --reuse-values -f server/controllers/alerts/KubeNodeDown.yaml prometheus prometheus-community/kube-prometheus-stack -n default';
51 | break;
52 | }
53 | fs_1.default.readFile(filePath, 'utf8', (err, data) => {
54 | if (err) {
55 | console.error(err);
56 | return res.status(500).send('Failed to read file');
57 | }
58 | let newData = type === 'Kube'
59 | ? data.replace(new RegExp(oldName, 'g'), name)
60 | : data
61 | .replace(new RegExp(oldText, 'g'), newText)
62 | .replace(new RegExp(oldName, 'g'), name);
63 | fs_1.default.writeFile(filePath, newData, 'utf8', (err) => {
64 | if (err) {
65 | console.error(err);
66 | return res.status(500).send('Failed to write file');
67 | }
68 | console.log('File updated successfully!');
69 | (0, child_process_1.exec)(command, (error, stdout, stderr) => {
70 | if (error) {
71 | console.error(`Error running command: ${error.message}`);
72 | return res.status(500).send('Failed to upgrade Prometheus chart');
73 | }
74 | if (stderr) {
75 | console.error(`Command error output: ${stderr}`);
76 | return res.status(500).send('Failed to upgrade Prometheus chart');
77 | }
78 | console.log(`Command output: ${stdout}`);
79 | const templateFilePath = filePath.replace('.yaml', '-template.yaml');
80 | fs_1.default.readFile(templateFilePath, 'utf8', (err, data) => {
81 | if (err) {
82 | console.error(err);
83 | return res.status(500).send('Failed to read template file');
84 | }
85 | fs_1.default.writeFile(filePath, data, 'utf8', (err) => {
86 | if (err) {
87 | console.error(err);
88 | return res
89 | .status(500)
90 | .send('Failed to replace file with template');
91 | }
92 | console.log('File replaced with template successfully!');
93 | return res.status(200).send('Alert created successfully');
94 | });
95 | });
96 | });
97 | });
98 | });
99 | });
100 | const alertController = {
101 | createAlert,
102 | };
103 | exports.default = alertController;
104 |
--------------------------------------------------------------------------------
/build/server/controllers/alerts/mergefile.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const yaml = require('js-yaml');
3 | const path = require('path');
4 | function mergeAlertYamlFiles() {
5 | // Get the full paths to the YAML files
6 | const kubeAPIDownPath = path.join(__dirname, 'KubeAPIDown.yaml');
7 | const kubeNodeDownPath = path.join(__dirname, 'KubeNodeDown.yaml');
8 | // Read in the contents of the two YAML files
9 | const kubeAPIDownYaml = fs.readFileSync(kubeAPIDownPath, 'utf8');
10 | const kubeNodeDownYaml = fs.readFileSync(kubeNodeDownPath, 'utf8');
11 | // Parse the YAML contents into objects
12 | const kubeAPIDownObj = yaml.load(kubeAPIDownYaml);
13 | const kubeNodeDownObj = yaml.load(kubeNodeDownYaml);
14 | // Merge the two objects together
15 | kubeAPIDownObj.additionalPrometheusRulesMap['custom-rules'].groups[0].rules.push(kubeNodeDownObj);
16 | // Convert the merged object back to YAML
17 | const mergedYaml = yaml.dump(kubeAPIDownObj);
18 | // Write the merged YAML back to the KubeAPIDown.yaml file
19 | fs.writeFileSync(kubeAPIDownPath, mergedYaml, 'utf8');
20 | console.log('Successfully merged YAML files!');
21 | }
22 | mergeAlertYamlFiles();
23 |
--------------------------------------------------------------------------------
/build/server/controllers/alerts/replacefile.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const { exec } = require('child_process');
3 | const path = require('path');
4 | const req = {};
5 | req.body = { type: 'Kube', threshold: 2, name: 'Kube Node Down' };
6 | const { type, threshold, name } = req.body;
7 | let filePath;
8 | let oldText;
9 | let newText;
10 | let oldName;
11 | let command;
12 | switch (type) {
13 | case 'CPU':
14 | filePath = path.join(__dirname, '/HighCPUUsage.yaml');
15 | oldText = /1\b/g;
16 | newText = `sum(rate(container_cpu_usage_seconds_total{namespace="default"}[5m])) by (pod_name) > ${threshold}`;
17 | oldName = 'HighCPUUsage';
18 | command =
19 | 'helm upgrade --reuse-values -f alerts/HighCPUUsage.yaml prometheus prometheus-community/kube-prometheus-stack -n default';
20 | break;
21 | case 'Memory':
22 | filePath = path.join(__dirname, '/HighMemoryUsage.yaml');
23 | oldText =
24 | 'sum(container_memory_working_set_bytes{namespace="default"}) by (pod_name) > 1e+09';
25 | newText = `sum(container_memory_working_set_bytes{namespace="default"}) by (pod_name) > ${threshold}e+09`;
26 | oldName = 'HighMemoryUsage';
27 | command =
28 | 'helm upgrade --reuse-values -f alerts/HighMemoryUsage.yaml prometheus prometheus-community/kube-prometheus-stack -n default';
29 | break;
30 | default:
31 | filePath = path.join(__dirname, '/KubeNodeDown.yaml');
32 | oldText = '';
33 | newText = '';
34 | oldName = 'KubeNodeDown';
35 | command =
36 | 'helm upgrade --reuse-values -f alerts/KubeNodeDown.yaml prometheus prometheus-community/kube-prometheus-stack -n default';
37 | break;
38 | }
39 | fs.readFile(filePath, 'utf8', (err, data) => {
40 | if (err) {
41 | console.error(err);
42 | return;
43 | }
44 | let newData = type === 'Kube'
45 | ? data.replace(new RegExp(oldName, 'g'), name)
46 | : data
47 | .replace(new RegExp('1', 'g'), threshold)
48 | .replace(new RegExp(oldName, 'g'), name);
49 | fs.writeFile(filePath, newData, 'utf8', (err) => {
50 | if (err) {
51 | console.error(err);
52 | return;
53 | }
54 | console.log('File updated successfully!');
55 | exec(command, (error, stdout, stderr) => {
56 | if (error) {
57 | console.error(`Error running command: ${error.message}`);
58 | return;
59 | }
60 | if (stderr) {
61 | console.error(`Command error output: ${stderr}`);
62 | return;
63 | }
64 | console.log(`Command output: ${stdout}`);
65 | });
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/build/server/controllers/apiController.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 | return new (P || (P = Promise))(function (resolve, reject) {
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
9 | });
10 | };
11 | Object.defineProperty(exports, "__esModule", { value: true });
12 | const apiController = {};
13 | apiController.getApi = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
14 | console.log('received get request');
15 | try {
16 | let response = yield fetch('http://localhost:3001/api/auth/keys', {
17 | method: 'POST',
18 | mode: 'no-cors',
19 | headers: {
20 | Authorization: 'Basic ' + Buffer.from('admin:prom-operator').toString('base64'),
21 | Accept: '*/*',
22 | 'Content-Type': 'application/json',
23 | },
24 | body: JSON.stringify({
25 | name: Math.random().toString(36).substring(7),
26 | role: 'Admin',
27 | secondsToLive: 86400,
28 | }),
29 | })
30 | .then((res) => res.json())
31 | .then((data) => {
32 | res.locals.key = data.key;
33 | console.log('returned key:', data.key);
34 | });
35 | return next();
36 | }
37 | catch (error) {
38 | return next(error);
39 | }
40 | });
41 | apiController.getUid = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
42 | console.log('received uid request');
43 | console.log(req.body);
44 | const { key, dashboard } = req.body;
45 | try {
46 | let response = yield fetch(`http://localhost:3001/api/search?query=${encodeURIComponent(dashboard)}`, {
47 | method: 'GET',
48 | headers: {
49 | Authorization: `Bearer ${key}`,
50 | 'Content-Type': 'application/json',
51 | },
52 | })
53 | .then((res) => res.json())
54 | .then((data) => {
55 | // Get the uid of the first dashboard in the list
56 | const uid = data[0].uid;
57 | res.locals.uid = uid;
58 | });
59 | return next();
60 | }
61 | catch (error) {
62 | return next(error);
63 | }
64 | });
65 | exports.default = apiController;
66 |
--------------------------------------------------------------------------------
/build/server/controllers/clusterController.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 | return new (P || (P = Promise))(function (resolve, reject) {
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
9 | });
10 | };
11 | Object.defineProperty(exports, "__esModule", { value: true });
12 | const client_node_1 = require("@kubernetes/client-node");
13 | const kc = new client_node_1.KubeConfig();
14 | kc.loadFromDefault();
15 | const k8sApi2 = kc.makeApiClient(client_node_1.AppsV1Api);
16 | const k8sApi = kc.makeApiClient(client_node_1.CoreV1Api);
17 | const k8sApi3 = kc.makeApiClient(client_node_1.NetworkingV1Api);
18 | ;
19 | const clusterController = {};
20 | const getNodes = () => __awaiter(void 0, void 0, void 0, function* () {
21 | const res = yield k8sApi.listNode();
22 | console.log(res.body.items[0]);
23 | const nodes = res.body.items.map((data) => {
24 | const { name, namespace, uid, labels } = data.metadata;
25 | const creationTimeStamp = data.metadata.creationTimestamp;
26 | const { configSource, providerID } = data.spec;
27 | const { status } = data;
28 | const response = {
29 | name,
30 | namespace,
31 | uid,
32 | creationTimeStamp,
33 | labels,
34 | configSource,
35 | providerID,
36 | status,
37 | };
38 | // console.log(response);
39 | return response;
40 | });
41 | return nodes;
42 | });
43 | // getNodes();
44 | const getPods = () => __awaiter(void 0, void 0, void 0, function* () {
45 | const res = yield k8sApi.listPodForAllNamespaces();
46 | //console.log(res.body.items[0].metadata.labels);
47 | const pods = res.body.items.map((data) => {
48 | const { name, namespace, uid, creationTimestamp, labels } = data.metadata;
49 | const { containers, nodeName, serviceAccount } = data.spec;
50 | const { containerStatuses, hostIP, podIP, startTime } = data.status;
51 | const containersInfo = containers.map((container) => ({
52 | image: container.image,
53 | name: container.name,
54 | }));
55 | const response = {
56 | name,
57 | namespace,
58 | uid,
59 | creationTimestamp,
60 | labels,
61 | containersInfo,
62 | nodeName,
63 | serviceAccount,
64 | containerStatuses,
65 | hostIP,
66 | podIP,
67 | startTime,
68 | };
69 | return response;
70 | });
71 | return pods;
72 | });
73 | // getPods();
74 | const getNamespaces = () => __awaiter(void 0, void 0, void 0, function* () {
75 | const res = yield k8sApi.listNamespace();
76 | const test = res.body.items
77 | .filter((namespace) => namespace.metadata.name.slice(0, 4) !== 'kube')
78 | .map((namespace) => {
79 | const { creationTimeStamp } = namespace;
80 | return {
81 | creationTimeStamp: creationTimeStamp,
82 | name: namespace.metadata.name,
83 | id: namespace.metadata.uid,
84 | };
85 | });
86 | return test;
87 | });
88 | const getServices = () => __awaiter(void 0, void 0, void 0, function* () {
89 | const res = yield k8sApi.listServiceForAllNamespaces();
90 | const response = res.body.items;
91 | // //console.log(res.body.items[0]);
92 | const results = response.map((data) => {
93 | //console.log('service name:', data.metadata.name);
94 | const { name, uid, creationTimeStamp, namespace } = data.metadata;
95 | const { ipFamilies, ports, selector, type } = data.spec;
96 | const result = {
97 | name,
98 | uid,
99 | creationTimeStamp,
100 | namespace,
101 | ipFamilies,
102 | ports,
103 | selector,
104 | type,
105 | };
106 | return result;
107 | });
108 | // console.log(results);
109 | return results;
110 | });
111 | // getServices();
112 | const getDeployments = () => __awaiter(void 0, void 0, void 0, function* () {
113 | const res = yield k8sApi2.listDeploymentForAllNamespaces();
114 | const response = res.body.items;
115 | //console.log(response[0].spec.selector.matchLabels);
116 | const results = response.map((data) => {
117 | // console.log(data.spec.template);
118 | const { name, uid, creationTimeStamp, namespace } = data.metadata;
119 | const { strategy, replicas, selector: matchLabels } = data.spec;
120 | const { availableReplicas, conditions } = data.status;
121 | const result = {
122 | name,
123 | uid,
124 | creationTimeStamp,
125 | namespace,
126 | strategy,
127 | matchLabels,
128 | replicas,
129 | availableReplicas,
130 | conditions,
131 | };
132 | return result;
133 | });
134 | // console.log(results);
135 | return results;
136 | });
137 | // getDeployments();
138 | const getIngresses = () => __awaiter(void 0, void 0, void 0, function* () {
139 | const res = yield k8sApi3.listIngressForAllNamespaces();
140 | return res.body.items;
141 | });
142 | clusterController.getClusterInfo = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
143 | try {
144 | const nodes = yield getNodes();
145 | const pods = yield getPods();
146 | const namespaces = yield getNamespaces();
147 | const services = yield getServices();
148 | const deployments = yield getDeployments();
149 | const ingresses = yield getIngresses();
150 | const clusterInfo = {
151 | nodes,
152 | pods,
153 | namespaces,
154 | services,
155 | deployments,
156 | ingresses,
157 | };
158 | //console.log('server side nodes', clusterInfo.nodes);
159 | res.locals.clusterInfo = clusterInfo;
160 | return next();
161 | }
162 | catch (err) {
163 | return next({
164 | log: `clusterController.getNamespaces ERROR: ${err}`,
165 | status: 500,
166 | message: { err: 'An error occurred in getClusterInfo' },
167 | });
168 | }
169 | });
170 | exports.default = clusterController;
171 |
--------------------------------------------------------------------------------
/build/server/controllers/setupController.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 | return new (P || (P = Promise))(function (resolve, reject) {
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
9 | });
10 | };
11 | Object.defineProperty(exports, "__esModule", { value: true });
12 | const child_process_1 = require("child_process");
13 | const setupController = {};
14 | // helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
15 | // helm repo update
16 | // helm install prometheus prometheus-community/kube-prometheus-stack
17 | // kubectl port-forward prometheus-grafana-5f98c899f8-tv8gp 3001:3000
18 | setupController.promInit = (req, res, next) => {
19 | console.log('\n\nPrometheus Setup Starting\n\n');
20 | (0, child_process_1.spawnSync)('helm repo add prometheus-community https://prometheus-community.github.io/helm-charts', {
21 | stdio: 'inherit',
22 | shell: true,
23 | });
24 | (0, child_process_1.spawnSync)('helm repo update', {
25 | stdio: 'inherit',
26 | shell: true,
27 | });
28 | (0, child_process_1.spawnSync)('helm install prometheus prometheus-community/kube-prometheus-stack', {
29 | stdio: 'inherit',
30 | shell: true,
31 | });
32 | return next();
33 | };
34 | setupController.grafEmbed = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
35 | console.log('\n\nGrafana Setup Starting\n\n');
36 | let podName;
37 | const getter = (0, child_process_1.exec)('kubectl get pods', (err, stdout, stderr) => {
38 | if (err) {
39 | console.error(`exec error: ${err}`);
40 | return;
41 | }
42 | if (stderr) {
43 | console.log(`stderr: ${stderr}`);
44 | return;
45 | }
46 | const output = stdout.split('\n');
47 | output.forEach((line) => {
48 | if (line.includes('prometheus-grafana')) {
49 | [podName] = line.split(' ');
50 | }
51 | });
52 | console.log(podName);
53 | });
54 | // kubectl replace -f prometheus-grafana.yaml
55 | // execSync(`kubectl delete pod ${podName}`, {
56 | // // stdio: 'inherit',
57 | // // shell: true
58 | // });
59 | getter.once('close', () => {
60 | (0, child_process_1.spawnSync)('kubectl apply -f prometheus-grafana.yaml', {
61 | stdio: 'inherit',
62 | shell: true,
63 | });
64 | (0, child_process_1.spawnSync)(`kubectl delete pod ${podName}`, {
65 | stdio: 'inherit',
66 | shell: true,
67 | });
68 | // setupController.forwardPort();
69 | return next();
70 | });
71 | });
72 | setupController.forwardPorts = (req, res, next) => {
73 | console.log('\n\nForwarding Ports\n\n');
74 | let grafPod, promPod, alertPod;
75 | let podStatus;
76 | while (podStatus !== 'Running') {
77 | const abc = (0, child_process_1.execSync)('kubectl get pods');
78 | abc
79 | .toString()
80 | .split('\n')
81 | .forEach((line) => {
82 | if (!promPod && line.includes('prometheus-0'))
83 | [grafPod] = line.split(' ');
84 | if (!alertPod && line.includes('alertmanager-0'))
85 | [alertPod] = line.split(' ');
86 | if (line.includes('prometheus-grafana')) {
87 | if (line.includes('Running'))
88 | podStatus = 'Running';
89 | [grafPod] = line.split(' ');
90 | }
91 | });
92 | }
93 | const ports = (0, child_process_1.spawn)(`kubectl port-forward ${grafPod} 3001:3000 && kubectl port-forward ${promPod} 9090 && kubectl port-forward ${alertPod} 9093`, { shell: true, });
94 | ports.stdout.on('data', (data) => {
95 | console.log(`stdout: ${data}`);
96 | });
97 | ports.stderr.on('data', (data) => {
98 | console.error(`grafana port forwarding error: ${data}`);
99 | });
100 | return next();
101 | };
102 | exports.default = setupController;
103 |
--------------------------------------------------------------------------------
/build/server/routes/alerts.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const express_1 = __importDefault(require("express"));
7 | const alertController_1 = __importDefault(require("../controllers/alertController"));
8 | const router = express_1.default.Router();
9 | router.post('/', alertController_1.default.createAlert, (req, res) => {
10 | res.sendStatus(200);
11 | });
12 | exports.default = router;
13 |
--------------------------------------------------------------------------------
/build/server/routes/cluster.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const express_1 = __importDefault(require("express"));
7 | const clusterController_1 = __importDefault(require("../controllers/clusterController"));
8 | const router = express_1.default.Router();
9 | router.get('/', clusterController_1.default.getClusterInfo, (req, res) => {
10 | res.status(200).json(res.locals.clusterInfo);
11 | });
12 | exports.default = router;
13 |
--------------------------------------------------------------------------------
/build/server/routes/grafana.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const express_1 = __importDefault(require("express"));
7 | const apiController_1 = __importDefault(require("../controllers/apiController"));
8 | const router = express_1.default.Router();
9 | router.get('/key', apiController_1.default.getApi, (req, res) => {
10 | res.status(200).json(res.locals.key);
11 | });
12 | router.post('/uid', apiController_1.default.getUid, (req, res) => {
13 | res.status(200).json(res.locals.uid);
14 | });
15 | exports.default = router;
16 |
--------------------------------------------------------------------------------
/build/server/routes/setup.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const express_1 = __importDefault(require("express"));
7 | const setupController_1 = __importDefault(require("../controllers/setupController"));
8 | const router = express_1.default.Router();
9 | router.get('/promSetup', setupController_1.default.promInit, (req, res) => {
10 | res.sendStatus(200);
11 | });
12 | router.get('/grafSetup', setupController_1.default.grafEmbed, (req, res) => {
13 | res.sendStatus(200);
14 | });
15 | router.get('/forwardPorts', setupController_1.default.forwardPorts, (req, res) => {
16 | res.sendStatus(200);
17 | });
18 | exports.default = router;
19 |
--------------------------------------------------------------------------------
/build/server/schema/schema.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.schema = void 0;
4 | const graphql_1 = require("graphql");
5 | const schema = (0, graphql_1.buildSchema)(`
6 | type Query {
7 | key: String!
8 | uid(key: String!, dashboard: String!): String!
9 | }
10 | `);
11 | exports.schema = schema;
12 |
--------------------------------------------------------------------------------
/build/server/server.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const express_1 = __importDefault(require("express"));
7 | const cors_1 = __importDefault(require("cors"));
8 | const cluster_1 = __importDefault(require("./routes/cluster"));
9 | const grafana_1 = __importDefault(require("./routes/grafana"));
10 | const setup_1 = __importDefault(require("./routes/setup"));
11 | const alerts_1 = __importDefault(require("./routes/alerts"));
12 | const app = (0, express_1.default)();
13 | const PORT = 3000;
14 | app.use(express_1.default.json());
15 | app.use((0, cors_1.default)());
16 | app.use('/setup', setup_1.default);
17 | app.use('/clusterdata', cluster_1.default);
18 | app.use('/grafana', grafana_1.default);
19 | app.use('/alerts', alerts_1.default);
20 | // catch all
21 | app.use((req, res) => res.sendStatus(404));
22 | // global err handler
23 | app.use((err, req, res, next) => {
24 | const defaultErr = {
25 | log: 'Express error handler caught unknown middleware error',
26 | status: 400,
27 | message: { err: 'An error occurred' },
28 | };
29 | const errorObj = Object.assign({ defaultErr }, err);
30 | console.log(errorObj.log);
31 | return res.status(errorObj.status).json(errorObj.message);
32 | });
33 | app.listen(PORT.toString(), () => console.log(`Server listening on port ${PORT}`));
34 | exports.default = app;
35 |
--------------------------------------------------------------------------------
/client/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Navigate, Routes, Route } from 'react-router-dom';
3 | import Dashboard from './components/dashboard/Dashboard';
4 | import Setup from './Pages/Setup';
5 |
6 |
7 | function App() {
8 | return (
9 |
10 |
11 | } />
12 | } />
13 | }
16 | />{' '}
17 | // removed exact from path
18 |
19 |
20 | );
21 | }
22 |
23 | export default App;
24 |
--------------------------------------------------------------------------------
/client/Pages/Alerts.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 |
4 | function Alerts() {
5 | return (
6 |
7 |
8 |
15 |
16 |
17 | );
18 | }
19 |
20 | export default Alerts;
--------------------------------------------------------------------------------
/client/Pages/CustomAlerts.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Particle from './Particle';
3 |
4 | function CustomAlerts() {
5 | const [selectedAlertOption, setSelectedAlertOption] = useState('');
6 | const [submittedAlertOption, setSubmittedAlertOption] = useState('');
7 | const [selectedMemory, setSelectedMemory] = useState('');
8 | const [submittedMemory, setsubmittedMemory] = useState('');
9 | const [selectedCPU, setSelectedCPU] = useState('');
10 | const [submittedCPU, setSubmittedCPU] = useState('');
11 |
12 | const [alertName, setAlertName] = useState('');
13 |
14 | const handleTypeSubmit = (event: any) => {
15 | event.preventDefault();
16 | setSubmittedAlertOption(selectedAlertOption);
17 | };
18 |
19 | const handleRadioChange = (event: any) => {
20 | setSelectedAlertOption(event.target.value);
21 | };
22 |
23 | const handleMemoryChange = (event: any) => {
24 | setSelectedMemory(event.target.value);
25 | };
26 |
27 | const handleMemorySubmit = (event: any) => {
28 | event.preventDefault();
29 | setsubmittedMemory(selectedMemory);
30 | };
31 |
32 | const handleCPUChange = (event: any) => {
33 | setSelectedCPU(event.target.value);
34 | };
35 |
36 | const handleCPUSubmit = (event: any) => {
37 | event.preventDefault();
38 | setSubmittedCPU(selectedCPU);
39 | };
40 |
41 | const handleNameChange = (event: any) => {
42 | setAlertName(event.target.value);
43 | };
44 |
45 | const handleFormSubmit = (event: any) => {
46 | event.preventDefault();
47 | event.preventDefault();
48 | setSubmittedAlertOption(selectedAlertOption);
49 | const threshold =
50 | submittedAlertOption === 'Memory' ? submittedMemory : submittedCPU;
51 | const result = { name: alertName, type: submittedAlertOption, threshold };
52 | setsubmittedMemory('');
53 | setSelectedMemory('');
54 | setSubmittedCPU('');
55 | setSelectedCPU('');
56 | setAlertName('');
57 | setSelectedAlertOption('');
58 | setSubmittedAlertOption('');
59 | fetch('http://localhost:3000/alerts', {
60 | method: 'POST',
61 | headers: {
62 | 'Content-Type': 'application/json',
63 | },
64 | body: JSON.stringify(result),
65 | })
66 | .then((res) => res.json())
67 | .then((data) => {
68 | console.log(data);
69 | })
70 | .catch((error) => {
71 | console.error('Error:', error);
72 | });
73 | };
74 |
75 | return (
76 |
77 |
79 | {submittedAlertOption === '' && (
80 |
81 |
Select Alert Type
82 |
119 |
120 | )}
121 | {submittedAlertOption === 'Memory' && submittedMemory === '' && (
122 |
123 |
Memory Threshold
124 |
137 |
138 | )}
139 |
140 | {(submittedMemory !== '' ||
141 | submittedCPU !== '' ||
142 | submittedAlertOption === 'Kube') && (
143 |
144 |
Create Alert Name
145 |
161 |
162 | )}
163 |
164 | {submittedAlertOption === 'CPU' && submittedCPU === '' && (
165 |
166 |
CPU Threshold
167 |
181 |
182 | )}
183 |
184 | );
185 | }
186 |
187 | export default CustomAlerts;
188 |
--------------------------------------------------------------------------------
/client/Pages/Grafana/Cluster.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | type Clusterprops = {
5 | apiKey: string | any,
6 | }
7 |
8 | function Cluster({ apiKey } : Clusterprops) {
9 | const [uid, setUid] = useState(null);
10 | const now = new Date().getTime();
11 | const from = new Date(now - 60 * 60 * 1000).getTime();
12 |
13 | useEffect(() => {
14 | fetch('http://localhost:3000/grafana/uid', {
15 | method: 'POST',
16 | headers: {
17 | 'Content-Type': 'application/json',
18 | },
19 | body: JSON.stringify({
20 | key: apiKey,
21 | dashboard: 'Kubernetes / API server',
22 | }),
23 | })
24 | .then((res) => res.json())
25 | .then((data) => {
26 | console.log('kubernetes-api',data);
27 | setUid(data);
28 | });
29 | }, []);
30 |
31 | const url = `http://localhost:3001/d/${uid}/kubernetes-api-server?orgId=1&refresh=10s&from=${from}&to=${now}&kiosk=true`;
32 |
33 | return (
34 |
35 |
42 |
43 | );
44 | }
45 |
46 | export default Cluster;
47 |
48 | // http://localhost:3001/d/${uid}/kubernetes-api-server?orgId=1&refresh=10s&from=1677342274613&to=1677345874613&kiosk=true&theme=light
--------------------------------------------------------------------------------
/client/Pages/Grafana/ClusterUseMethod.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | type ClusterUseMethodProps = {
5 | apiKey: string | any,
6 | }
7 | function ClusterUseMethod({ apiKey } : ClusterUseMethodProps) {
8 | const [uid, setUid] = useState(null);
9 | const now = new Date().getTime();
10 | const from = new Date(now - 60 * 60 * 1000).getTime();
11 |
12 | useEffect(() => {
13 | fetch('http://localhost:3000/grafana/uid', {
14 | method: 'POST',
15 | headers: {
16 | 'Content-Type': 'application/json',
17 | },
18 | body: JSON.stringify({
19 | key: apiKey,
20 | dashboard: 'Node Exporter / USE Method / Cluster',
21 | }),
22 | })
23 | .then((res) => res.json())
24 | .then((data) => {
25 | setUid(data);
26 | });
27 | }, [apiKey]);
28 | const url = `http://localhost:3001/d/${uid}/node-exporter-use-method-cluster?orgId=1&refresh=30s&from=${from}&to=${now}&kiosk=true&theme=light`;
29 | return (
30 |
31 |
32 |
33 | );
34 | }
35 |
36 | export default ClusterUseMethod;
37 |
--------------------------------------------------------------------------------
/client/Pages/Grafana/CoreDNS.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | type CoreDNSProps = {
5 | apiKey: string | any,
6 | }
7 |
8 | function CoreDNS({ apiKey } : CoreDNSProps) {
9 | const [uid, setUid] = useState(null);
10 | const now = new Date().getTime();
11 | const from = new Date(now - 60 * 60 * 1000).getTime();
12 |
13 | useEffect(() => {
14 | fetch('http://localhost:3000/grafana/uid', {
15 | method: 'POST',
16 | headers: {
17 | 'Content-Type': 'application/json',
18 | },
19 | body: JSON.stringify({
20 | key: apiKey,
21 | dashboard: 'CoreDNS',
22 | }),
23 | })
24 | .then((res) => res.json())
25 | .then((data) => {
26 | setUid(data);
27 | });
28 | }, [apiKey]);
29 | const url = `http://localhost:3001/d/${uid}/coredns?orgId=1&refresh=10s&from=${from}&to=${now}&kiosk=true&theme=light`;
30 | return (
31 |
32 |
33 |
34 | );
35 | }
36 |
37 | export default CoreDNS;
38 |
--------------------------------------------------------------------------------
/client/Pages/Grafana/Kubelet.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | type KubeletProps = {
5 | apiKey: string | any,
6 | }
7 |
8 | function Kubelet({ apiKey } : KubeletProps) {
9 | const [uid, setUid] = useState(null);
10 | const now = new Date().getTime();
11 | const from = new Date(now - 60 * 60 * 1000).getTime();
12 |
13 | useEffect(() => {
14 | fetch('http://localhost:3000/grafana/uid', {
15 | method: 'POST',
16 | headers: {
17 | 'Content-Type': 'application/json',
18 | },
19 | body: JSON.stringify({
20 | key: apiKey,
21 | dashboard: 'Kubernetes / Kubelet',
22 | }),
23 | })
24 | .then((res) => res.json())
25 | .then((data) => {
26 | setUid(data);
27 | });
28 | }, [apiKey]);
29 | const url = `http://localhost:3001/d/${uid}/kubernetes-kubelet?orgId=1&refresh=10s&from=${from}&to=${now}&kiosk=true&theme=light`;
30 |
31 | return (
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | export default Kubelet;
39 |
--------------------------------------------------------------------------------
/client/Pages/Grafana/NodeUseMethod.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | type NodeUseMethodProps = {
5 | apiKey: string | any,
6 | }
7 |
8 | function NodeUseMethod({ apiKey } : NodeUseMethodProps) {
9 | const [uid, setUid] = useState(null);
10 | const now = new Date().getTime();
11 | const from = new Date(now - 60 * 60 * 1000).getTime();
12 |
13 | useEffect(() => {
14 | fetch('http://localhost:3000/grafana/uid', {
15 | method: 'POST',
16 | headers: {
17 | 'Content-Type': 'application/json',
18 | },
19 | body: JSON.stringify({
20 | key: apiKey,
21 | dashboard: 'Node Exporter / USE Method / Node',
22 | }),
23 | })
24 | .then((res) => res.json())
25 | .then((data) => {
26 | setUid(data);
27 | });
28 | }, [apiKey]);
29 | const url = `http://localhost:3001/d/${uid}/node-exporter-use-method-node?orgId=1&refresh=30s&from=${from}&to=${now}&kiosk=true&theme=light`;
30 |
31 | return (
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | export default NodeUseMethod;
39 |
--------------------------------------------------------------------------------
/client/Pages/Grafana/Nodes.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 |
4 | type NodesProps = {
5 | apiKey: string | any,
6 | }
7 |
8 | function Nodes({ apiKey } : NodesProps) {
9 | const [uid, setUid] = useState(null);
10 | const now = new Date().getTime();
11 | const from = new Date(now - 60 * 60 * 1000).getTime();
12 |
13 | useEffect(() => {
14 | fetch('http://localhost:3000/grafana/uid', {
15 | method: 'POST',
16 | headers: {
17 | 'Content-Type': 'application/json',
18 | },
19 | body: JSON.stringify({
20 | key: apiKey,
21 | dashboard: 'Node Exporter / Nodes',
22 | }),
23 | })
24 | .then((res) => res.json())
25 | .then((data) => {
26 | console.log('uid',data);
27 | setUid(data);
28 | });
29 | }, [apiKey]);
30 | const url = `http://localhost:3001/d/${uid}/node-exporter-nodes?orgId=1&refresh=&from=${from}&to=${now}&kiosk=true&theme=light`;
31 | // http://localhost:3001/d/deq7T0-Vk/node-exporter-nodes?orgId=1&from=1678715225768&to=1678715287956
32 | //p0W7JA-Vz
33 | return (
34 |
35 |
36 |
37 | );
38 | }
39 |
40 | export default Nodes;
41 |
--------------------------------------------------------------------------------
/client/Pages/Overview.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestVis from '../components/visualizer/Visualizer';
3 |
4 |
5 | function Overview() {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default Overview;
14 |
--------------------------------------------------------------------------------
/client/Pages/Particle.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useCallback } from "react";
3 | import Particles from "react-tsparticles";
4 | import type { Container, Engine } from "tsparticles-engine";
5 | import { loadFull } from "tsparticles";
6 |
7 | const Particle = () => {
8 | const particlesInit = useCallback(async (engine: Engine) => {
9 | console.log(engine);
10 |
11 | // you can initialize the tsParticles instance (engine) here, adding custom shapes or presets
12 | // this loads the tsparticles package bundle, it's the easiest method for getting everything ready
13 | // starting from v2 you can add only the features you need reducing the bundle size
14 | await loadFull(engine);
15 | }, []);
16 |
17 | const particlesLoaded = useCallback(async (container: Container | undefined) => {
18 | await console.log(container);
19 | }, []);
20 | return (
21 |
98 | );
99 | };
100 |
101 | export default Particle;
--------------------------------------------------------------------------------
/client/Pages/PromQuery.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 |
4 | function PromQuery() {
5 | return (
6 |
7 |
8 |
16 |
17 |
18 | );
19 | }
20 |
21 | export default PromQuery;
--------------------------------------------------------------------------------
/client/Pages/Setup.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SetupButtons from '../components/SetupButtons';
3 | import Particle from './Particle';
4 |
5 |
6 |
7 | function SetupPage() {
8 | return (
9 |
13 | );
14 | }
15 |
16 | export default SetupPage;
17 |
--------------------------------------------------------------------------------
/client/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/client/assets/favicon.ico
--------------------------------------------------------------------------------
/client/components/SetupButtons.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Watch } from 'react-loader-spinner';
3 | import { useNavigate } from 'react-router-dom';
4 | import logo from '../styles/logo-color-transformed.png';
5 |
6 | function Setup() {
7 | const navigate = useNavigate();
8 | const [loading, setLoading] = useState(false);
9 |
10 | return (
11 |
12 | {loading && (
13 |
14 |
Sorry for the wait, hope it doesn't drive you KuberNutties!
15 |
16 |
17 | )}
18 | {!loading && (
19 | <>
20 |

25 |
26 |
27 |
Need to setup prometheus in your cluster?
28 |
40 |
41 |
42 |
Need to setup grafana in your cluster?
43 |
55 |
56 |
57 |
58 |
Need to forward ports to make metrics available?
59 |
71 |
72 |
73 |
83 |
84 | >
85 | )}
86 |
87 | );
88 | }
89 |
90 | export default Setup;
91 |
--------------------------------------------------------------------------------
/client/components/dashboard/Banner.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 |
4 | type bannerProps = {
5 | title: string,
6 | }
7 |
8 | function Banner(props: bannerProps) {
9 | const { title } = props;
10 | return (
11 |
12 |
{title}
13 |
14 | );
15 | }
16 |
17 | export default Banner;
18 |
--------------------------------------------------------------------------------
/client/components/dashboard/Dashboard.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Navbar from './Navbar';
3 | import { ProSidebarProvider } from 'react-pro-sidebar';
4 |
5 |
6 | function Dashboard() {
7 | const [api, setApi] = useState('');
8 | useEffect(() => {
9 | fetch('http://localhost:3000/grafana/key', {
10 | method: 'GET',
11 | })
12 | .then((res) => res.json())
13 | .then((data) => {
14 | console.log('key', data);
15 | setApi(data);
16 | })
17 | .catch((error) => {
18 | console.log('error when fetching api key', error);
19 | });
20 | }, []);
21 | return (
22 |
27 | );
28 | }
29 |
30 | export default Dashboard;
31 |
--------------------------------------------------------------------------------
/client/components/visualizer/icons/deployment-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
104 |
--------------------------------------------------------------------------------
/client/components/visualizer/icons/namespace-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
97 |
--------------------------------------------------------------------------------
/client/components/visualizer/icons/pod-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
115 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | kubernetes
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/client/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRoot } from 'react-dom/client';
3 | import { BrowserRouter } from 'react-router-dom';
4 | import App from './App';
5 | import 'bootstrap';
6 | import 'bootstrap/dist/css/bootstrap.min.css';
7 | import './styles/index.css';
8 |
9 | const root = createRoot(document.getElementById('mainBody'));
10 | root.render(
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/client/styles/logo-color-transformed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/client/styles/logo-color-transformed.png
--------------------------------------------------------------------------------
/client/styles/logo-color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/client/styles/logo-color.png
--------------------------------------------------------------------------------
/custom.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg' {
2 | const content: any;
3 | export default content;
4 | }
5 | declare module "react-graph-vis" {
6 | import { Network, NetworkEvents, Options, Node, Edge, DataSet } from "vis";
7 | import { Component } from "react";
8 |
9 | export { Network, NetworkEvents, Options, Node, Edge, DataSet } from "vis";
10 |
11 | export type graphEvents = {
12 | [event in NetworkEvents]: (params?: any) => void;
13 | };
14 |
15 |
16 | //Doesn't appear that this module supports passing in a vis.DataSet directly. Once it does graph can just use the Data object from vis.
17 | export interface graphData {
18 | nodes: Node[];
19 | edges: Edge[];
20 | }
21 |
22 | export interface NetworkGraphProps {
23 | graph: graphData;
24 | options?: Options;
25 | events?: graphEvents;
26 | getNetwork?: (network: Network) => void;
27 | identifier?: string;
28 | style?: React.CSSProperties;
29 | getNodes?: (nodes: DataSet) => void;
30 | getEdges?: (edges: DataSet) => void;
31 | }
32 |
33 | export interface NetworkGraphState {
34 | identifier: string;
35 | }
36 |
37 | export default class NetworkGraph extends Component<
38 | NetworkGraphProps,
39 | NetworkGraphState
40 | > {
41 | render() // is this the right one? jsx.element
42 | }
43 | }
--------------------------------------------------------------------------------
/cypress.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "cypress";
2 |
3 | export default defineConfig({
4 | e2e: {
5 | baseUrl: 'http://localhost:8080',
6 | setupNodeEvents(on, config) {
7 | // implement node event listeners here
8 | },
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/cypress/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "cypress"
4 | ],
5 | "rules": {
6 | "cypress/no-assigning-return-values": "error",
7 | "cypress/no-unnecessary-waiting": "error",
8 | "cypress/assertion-before-screenshot": "warn",
9 | "cypress/no-force": "warn",
10 | "cypress/no-async-tests": "error",
11 | "cypress/no-pause": "error"
12 | },
13 | "extends": [
14 | "plugin:cypress/recommended"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/cypress/e2e/spec.cy.ts:
--------------------------------------------------------------------------------
1 | describe('First Test', () => {
2 | it('Tests', () => {
3 | expect(true).to.equal(true);
4 | });
5 | });
6 |
7 | describe('Testing the access to the page', () => {
8 | it('Visits the Cluster Watch app', () => {
9 | cy.visit('/');
10 | });
11 | });
12 |
13 | describe('Settings Test', () => {
14 | it('Contains Setup for Prometheus ', () => {
15 | cy.visit('/');
16 | cy.contains('Setup Prometheus');
17 | });
18 | it('Contains Setup for Grafana ', () => {
19 | cy.visit('/');
20 | cy.contains('Setup Grafana');
21 | });
22 | it('Contains Setup for Port Forwarding ', () => {
23 | cy.visit('/');
24 | cy.contains('Start Port Forwarding');
25 | });
26 | });
27 |
28 | describe('Settings Dashboard access', () => {
29 | it('Contains access to Dashboard ', () => {
30 | cy.visit('/');
31 | cy.contains('Go to dashboard').click();
32 | cy.url().should('include', '/Dashboard/Dashboard/Overview');
33 | });
34 | });
35 |
36 | describe('Canvas', () => {
37 | beforeEach(() => {
38 | cy.visit('/Dashboard');
39 | });
40 | it('should hover over the center', () => {
41 | cy.get('canvas').click();
42 | cy.get('.vis-tooltip').should('be.visible');
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/cypress/support/commands.ts:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************
3 | // This example commands.ts shows you how to
4 | // create various custom commands and overwrite
5 | // existing commands.
6 | //
7 | // For more comprehensive examples of custom
8 | // commands please read more here:
9 | // https://on.cypress.io/custom-commands
10 | // ***********************************************
11 | //
12 | //
13 | // -- This is a parent command --
14 | // Cypress.Commands.add('login', (email, password) => { ... })
15 | //
16 | //
17 | // -- This is a child command --
18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
19 | //
20 | //
21 | // -- This is a dual command --
22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
23 | //
24 | //
25 | // -- This will overwrite an existing command --
26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
27 | //
28 | // declare global {
29 | // namespace Cypress {
30 | // interface Chainable {
31 | // login(email: string, password: string): Chainable
32 | // drag(subject: string, options?: Partial): Chainable
33 | // dismiss(subject: string, options?: Partial): Chainable
34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable
35 | // }
36 | // }
37 | // }
--------------------------------------------------------------------------------
/cypress/support/e2e.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/e2e.ts is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
--------------------------------------------------------------------------------
/iframe.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
27 | Node Exporter = "http://localhost:3001/d/YEPQHRbVz/node-exporter-nodes?kiosk&orgId=1&refresh=30s&from=1677163156470&to=1677166756470"
28 | Node Exporter metrics are important for monitoring Kubernetes clusters, as they provide information about the utilization of system resources such as CPU, memory, and disk usage, which is essential for identifying performance issues and optimizing resource allocation. I would rate the importance of Node Exporter metrics for Kubernetes at around 8 or 9 out of 10.
29 |
30 | CoreDNS = "http://localhost:3001/d/vkQ0UHxik/coredns?orgId=1&refresh=10s&from=1677156726881&to=1677167526881&kiosk=true"
31 | The importance of the CoreDNS dashboard metric for Kubernetes depends on your specific use case and the extent to which CoreDNS is being utilized within your cluster. For some deployments, CoreDNS may play a critical role in service discovery and network routing, making it important to monitor and track its performance. However, for other deployments that do not rely heavily on CoreDNS, this metric may be less important. In general, I would say the importance of this metric falls somewhere in the range of 4-7, depending on the context.
32 |
33 | Kubernetes API Server = "http://localhost:3001/d/09ec8aa1e996d6ffcd6817bbaff4db1b/kubernetes-api-server?orgId=1&refresh=10s&from=1677164164299&to=1677167764299&kiosk=true"
34 | The importance of the Kubernetes API Server metric in Grafana for Kubernetes depends on the specific use case and monitoring requirements.
35 | In general, the Kubernetes API Server is a critical component of a Kubernetes cluster, and it is responsible for managing the cluster state and providing an interface for cluster management operations. Therefore, monitoring the API server's performance and health can be important to ensure the overall health and availability of the Kubernetes cluster.
36 | On a scale of 0-10, the importance of the Kubernetes API Server metric in Grafana for Kubernetes could range from 7 to 10, depending on the specific use case and criticality of the Kubernetes cluster.
37 |
38 | Kubernetes Kublet = "http://localhost:3001/d/3138fa155d5915769fbded898ac09fd9/kubernetes-kubelet?orgId=1&refresh=10s&from=1677164554072&to=1677168154072&kiosk=true"
39 | On a scale of 0-10, I would say the importance of Kubernetes Kubelet metrics in Grafana for Kubernetes is around 7-8. The kubelet is a critical component of Kubernetes that runs on each node and is responsible for managing containers and ensuring they are running as intended. Metrics such as CPU and memory usage, network performance, and disk I/O are crucial for monitoring the health and performance of the kubelet and can provide valuable insights into the overall health of the cluster. However, the relative importance of these metrics may vary depending on the specific use case and workload being run on the cluster.
40 |
41 | Node Exporter / USE Method / Node = "http://localhost:3001/d/xHEwHRb4z/node-exporter-use-method-node?orgId=1&refresh=30s&from=1677164871992&to=1677168471992&kiosk=true"
42 | The importance of the Node Exporter / USE Method / Node metric in Grafana for Kubernetes depends on the specific use case and needs of the system being monitored. However, in general, this metric is important and can be assigned a score of 7-8 out of 10.
43 | The USE method is a widely recognized and accepted method for measuring the utilization of system resources, including CPU, memory, and I/O. By tracking these metrics at the node level, it is possible to gain insights into the overall health and performance of the system. This can be particularly valuable in a Kubernetes environment where nodes are often dynamic and can experience varying levels of load.
44 | Overall, the Node Exporter / USE Method / Node metric is a key performance indicator that can help to identify issues and optimize system performance, making it an important metric to monitor in a Kubernetes environment.
45 |
46 |
47 | Node Exporter / USE Method / Cluster = "http://localhost:3001/d/PtPwNRbVk/node-exporter-use-method-cluster?orgId=1&refresh=30s&from=1677165116649&to=1677168716649"
48 | The importance of the "Node Exporter / USE Method / Cluster" metric in Grafana for Kubernetes really depends on the specific needs and goals of the system being monitored.
49 | However, in general, this metric can be quite important for identifying performance bottlenecks, understanding how resources are being utilized across the cluster, and identifying potential issues related to CPU, memory, and disk usage.
50 | On a scale of 0-10, I would rate the importance of this metric at around 7 or 8.
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | kr8os
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | // '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
4 | '^.+\\.(ts|tsx|js|jsx)$': 'ts-jest',
5 | },
6 | moduleNameMapper: {
7 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
8 | '/__mocks__/fileMock.js',
9 | '\\.(css|less)$': '/__mocks__/styleMock.js',
10 | },
11 | testEnvironment: 'jsdom',
12 | };
13 |
--------------------------------------------------------------------------------
/launch-website/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/favicon.ico
--------------------------------------------------------------------------------
/launch-website/assets/img/bg-masthead.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/bg-masthead.jpg
--------------------------------------------------------------------------------
/launch-website/assets/img/jordy.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/jordy.jpg
--------------------------------------------------------------------------------
/launch-website/assets/img/kevin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/kevin.png
--------------------------------------------------------------------------------
/launch-website/assets/img/logo-color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/logo-color.png
--------------------------------------------------------------------------------
/launch-website/assets/img/logo-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/logo-white.png
--------------------------------------------------------------------------------
/launch-website/assets/img/mushrath.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/mushrath.png
--------------------------------------------------------------------------------
/launch-website/assets/img/portfolio/fullsize/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/portfolio/fullsize/1.png
--------------------------------------------------------------------------------
/launch-website/assets/img/portfolio/fullsize/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/portfolio/fullsize/2.png
--------------------------------------------------------------------------------
/launch-website/assets/img/portfolio/fullsize/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/portfolio/fullsize/3.png
--------------------------------------------------------------------------------
/launch-website/assets/img/portfolio/fullsize/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/portfolio/fullsize/4.png
--------------------------------------------------------------------------------
/launch-website/assets/img/portfolio/fullsize/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/portfolio/fullsize/5.png
--------------------------------------------------------------------------------
/launch-website/assets/img/portfolio/fullsize/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/portfolio/fullsize/6.png
--------------------------------------------------------------------------------
/launch-website/assets/img/portfolio/thumbnails/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/portfolio/thumbnails/1.png
--------------------------------------------------------------------------------
/launch-website/assets/img/portfolio/thumbnails/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/portfolio/thumbnails/2.png
--------------------------------------------------------------------------------
/launch-website/assets/img/portfolio/thumbnails/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/portfolio/thumbnails/3.png
--------------------------------------------------------------------------------
/launch-website/assets/img/portfolio/thumbnails/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/portfolio/thumbnails/4.png
--------------------------------------------------------------------------------
/launch-website/assets/img/portfolio/thumbnails/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/portfolio/thumbnails/5.png
--------------------------------------------------------------------------------
/launch-website/assets/img/portfolio/thumbnails/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/portfolio/thumbnails/6.png
--------------------------------------------------------------------------------
/launch-website/assets/img/sheng.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/launch-website/assets/img/sheng.png
--------------------------------------------------------------------------------
/launch-website/js/scripts.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Start Bootstrap - Creative v7.0.6 (https://startbootstrap.com/theme/creative)
3 | * Copyright 2013-2022 Start Bootstrap
4 | * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-creative/blob/master/LICENSE)
5 | */
6 | //
7 | // Scripts
8 | //
9 |
10 | window.addEventListener('DOMContentLoaded', event => {
11 |
12 | // Navbar shrink function
13 | var navbarShrink = function () {
14 | const navbarCollapsible = document.body.querySelector('#mainNav');
15 | if (!navbarCollapsible) {
16 | return;
17 | }
18 | if (window.scrollY === 0) {
19 | navbarCollapsible.classList.remove('navbar-shrink')
20 | } else {
21 | navbarCollapsible.classList.add('navbar-shrink')
22 | }
23 |
24 | };
25 |
26 | // Shrink the navbar
27 | navbarShrink();
28 |
29 | // Shrink the navbar when page is scrolled
30 | document.addEventListener('scroll', navbarShrink);
31 |
32 | // Activate Bootstrap scrollspy on the main nav element
33 | const mainNav = document.body.querySelector('#mainNav');
34 | if (mainNav) {
35 | new bootstrap.ScrollSpy(document.body, {
36 | target: '#mainNav',
37 | offset: 74,
38 | });
39 | };
40 |
41 | // Collapse responsive navbar when toggler is visible
42 | const navbarToggler = document.body.querySelector('.navbar-toggler');
43 | const responsiveNavItems = [].slice.call(
44 | document.querySelectorAll('#navbarResponsive .nav-link')
45 | );
46 | responsiveNavItems.map(function (responsiveNavItem) {
47 | responsiveNavItem.addEventListener('click', () => {
48 | if (window.getComputedStyle(navbarToggler).display !== 'none') {
49 | navbarToggler.click();
50 | }
51 | });
52 | });
53 |
54 | // Activate SimpleLightbox plugin for portfolio items
55 | new SimpleLightbox({
56 | elements: '#portfolio a.portfolio-box'
57 | });
58 |
59 | });
60 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kr8os",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.tsx",
6 | "scripts": {
7 | "test": "jest --verbose",
8 | "build": "NODE_ENV=production webpack",
9 | "start": "concurrently \"redis-server\" \"NODE_ENV=production nodemon ./server/server.ts\"",
10 | "dev": "concurrently \"redis-server\" \"nodemon ./server/server.ts\" \"webpack serve --hot --open\"",
11 | "cypress": "cypress open"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/oslabs-beta/kr8os.git"
16 | },
17 | "keywords": [],
18 | "author": "",
19 | "license": "ISC",
20 | "bugs": {
21 | "url": "https://github.com/oslabs-beta/kr8os/issues"
22 | },
23 | "homepage": "https://github.com/oslabs-beta/kr8os#readme",
24 | "dependencies": {
25 | "@babel/preset-typescript": "^7.21.0",
26 | "@mui/icons-material": "^5.11.9",
27 | "@mui/material": "^5.11.10",
28 | "babel-loader": "^9.1.2",
29 | "bootstrap": "^5.3.0-alpha1",
30 | "cors": "^2.8.5",
31 | "css-loader": "^6.7.3",
32 | "express": "^4.18.2",
33 | "file-loader": "^6.2.0",
34 | "ioredis": "^5.3.1",
35 | "nodemon": "^2.0.20",
36 | "react": "^18.2.0",
37 | "react-bootstrap": "^2.7.2",
38 | "react-dom": "^18.2.0",
39 | "react-loader-spinner": "^5.3.4",
40 | "react-pro-sidebar": "^1.0.0",
41 | "react-router-dom": "^6.8.1",
42 | "react-tsparticles": "^2.9.3",
43 | "redis": "^4.6.5",
44 | "tsparticles": "^2.9.3",
45 | "uuid": "^8.3.2"
46 | },
47 | "devDependencies": {
48 | "@babel/plugin-syntax-jsx": "^7.18.6",
49 | "@babel/plugin-transform-react-jsx": "^7.21.0",
50 | "@babel/preset-env": "^7.20.2",
51 | "@babel/preset-react": "^7.18.6",
52 | "@emotion/react": "^11.10.6",
53 | "@emotion/styled": "^11.10.6",
54 | "@kubernetes/client-node": "^0.18.1",
55 | "@testing-library/jest-dom": "^5.16.5",
56 | "@testing-library/react": "^14.0.0",
57 | "@testing-library/user-event": "^14.4.3",
58 | "@types/cors": "^2.8.13",
59 | "@types/fs-extra": "^11.0.1",
60 | "@types/mocha": "^10.0.1",
61 | "@types/react": "^18.0.28",
62 | "@types/react-dom": "^18.0.11",
63 | "@types/vis": "^4.21.24",
64 | "babel-jest": "^29.5.0",
65 | "concurrently": "^7.6.0",
66 | "cypress": "^12.7.0",
67 | "eslint": "^8.34.0",
68 | "eslint-config-airbnb": "^19.0.4",
69 | "eslint-plugin-import": "^2.27.5",
70 | "eslint-plugin-jsx-a11y": "^6.7.1",
71 | "eslint-plugin-react": "^7.32.2",
72 | "eslint-plugin-react-hooks": "^4.6.0",
73 | "html-loader": "^4.2.0",
74 | "html-webpack-plugin": "^5.5.0",
75 | "jest": "^29.5.0",
76 | "jest-environment-jsdom": "^29.5.0",
77 | "jest-svg-transformer": "^1.0.0",
78 | "jest-webpack": "^0.5.1",
79 | "raw-loader": "^4.0.2",
80 | "react-graph-vis": "^1.0.7",
81 | "style-loader": "^3.3.1",
82 | "supertest": "^6.3.3",
83 | "svg-inline-loader": "^0.8.2",
84 | "ts-jest": "^29.0.5",
85 | "ts-loader": "^9.4.2",
86 | "ts-node": "^10.9.1",
87 | "typescript": "^4.9.5",
88 | "use-resize-observer": "^9.1.0",
89 | "webpack": "^5.75.0",
90 | "webpack-cli": "^5.0.1",
91 | "webpack-dev-server": "^4.11.1",
92 | "whatwg-fetch": "^3.6.2"
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/prometheus-grafana.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | data:
3 | grafana.ini: |
4 | [analytics]
5 | check_for_updates = true
6 | [grafana_net]
7 | url = https://grafana.net
8 | [log]
9 | mode = console
10 | [paths]
11 | data = /var/lib/grafana/
12 | logs = /var/log/grafana
13 | plugins = /var/lib/grafana/plugins
14 | provisioning = /etc/grafana/provisioning
15 | [server]
16 | domain = ''
17 | [security]
18 | allow_embedding: true
19 | [auth.anonymous]
20 | enabled: true
21 | kind: ConfigMap
22 | metadata:
23 | annotations:
24 | meta.helm.sh/release-name: prometheus
25 | meta.helm.sh/release-namespace: default
26 | labels:
27 | app.kubernetes.io/instance: prometheus
28 | app.kubernetes.io/managed-by: Helm
29 | app.kubernetes.io/name: grafana
30 | app.kubernetes.io/version: 9.3.6
31 | helm.sh/chart: grafana-6.50.7
32 | name: prometheus-grafana
33 | namespace: default
--------------------------------------------------------------------------------
/readme_assets/alert-manager.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/readme_assets/alert-manager.png
--------------------------------------------------------------------------------
/readme_assets/custom-alert.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/readme_assets/custom-alert.gif
--------------------------------------------------------------------------------
/readme_assets/k8sapi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/readme_assets/k8sapi.png
--------------------------------------------------------------------------------
/readme_assets/linkedin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/readme_assets/linkedin.png
--------------------------------------------------------------------------------
/readme_assets/models/jordy.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/readme_assets/models/jordy.jpeg
--------------------------------------------------------------------------------
/readme_assets/models/kevin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/readme_assets/models/kevin.png
--------------------------------------------------------------------------------
/readme_assets/models/mushrath.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/readme_assets/models/mushrath.png
--------------------------------------------------------------------------------
/readme_assets/models/sheng.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/readme_assets/models/sheng.jpg
--------------------------------------------------------------------------------
/readme_assets/nodes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/readme_assets/nodes.png
--------------------------------------------------------------------------------
/readme_assets/prom.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/readme_assets/prom.gif
--------------------------------------------------------------------------------
/readme_assets/visualizer.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/readme_assets/visualizer.gif
--------------------------------------------------------------------------------
/server/controllers/alertController.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import { exec } from 'child_process';
3 | import path from 'path';
4 | import { Request, Response, NextFunction, RequestHandler } from 'express';
5 |
6 |
7 |
8 | const createAlert:RequestHandler = async (
9 | req: Request,
10 | res: Response,
11 | next: NextFunction
12 | ) => {
13 | const { type, threshold, name } :{type:string, threshold:number, name:string}= req.body;
14 |
15 | let filePath: string;
16 | let oldText: string | RegExp;
17 | let newText: string;
18 | let oldName: string;
19 | let command: string;
20 |
21 | switch (type) {
22 | case 'CPU':
23 | filePath = path.join(__dirname, 'alerts/HighCPUUsage.yaml');
24 | oldText = /1\b/g;
25 | newText = `sum(rate(container_cpu_usage_seconds_total{namespace="default"}[5m])) by (pod_name) > ${threshold}`;
26 | oldName = 'HighCPUUsage';
27 | command =
28 | 'helm upgrade --reuse-values -f server/controllers/alerts/HighCPUUsage.yaml prometheus prometheus-community/kube-prometheus-stack -n default';
29 | break;
30 | case 'Memory':
31 | filePath = path.join(__dirname, 'alerts/HighMemoryUsage.yaml');
32 | oldText =
33 | 'sum(container_memory_working_set_bytes{namespace="default"}) by (pod_name) > 1e+09';
34 | newText = `sum(container_memory_working_set_bytes{namespace="default"}) by (pod_name) > ${threshold}e+09`;
35 | oldName = 'HighMemoryUsage';
36 | command =
37 | 'helm upgrade --reuse-values -f server/controllers/alerts/HighMemoryUsage.yaml prometheus prometheus-community/kube-prometheus-stack -n default';
38 | break;
39 | default:
40 | filePath = path.join(__dirname, 'alerts/KubeNodeDown.yaml');
41 | oldText = '';
42 | newText = '';
43 | oldName = 'KubeNodeDown';
44 | command =
45 | 'helm upgrade --reuse-values -f server/controllers/alerts/KubeNodeDown.yaml prometheus prometheus-community/kube-prometheus-stack -n default';
46 | break;
47 | }
48 |
49 | fs.readFile(filePath, 'utf8', (err, data) => {
50 | if (err) {
51 | console.error(err);
52 | return res.status(500).send('Failed to read file');
53 | }
54 |
55 | let newData : string =
56 | type === 'Kube'
57 | ? data.replace(new RegExp(oldName, 'g'), name)
58 | : data
59 | .replace(new RegExp(oldText, 'g'), newText)
60 | .replace(new RegExp(oldName, 'g'), name);
61 |
62 | fs.writeFile(filePath, newData, 'utf8', (err) => {
63 | if (err) {
64 | console.error(err);
65 | return res.status(500).send('Failed to write file');
66 | }
67 | console.log('File updated successfully!');
68 |
69 | exec(command, (error, stdout, stderr) => {
70 | if (error) {
71 | console.error(`Error running command: ${error.message}`);
72 | return res.status(500).send('Failed to upgrade Prometheus chart');
73 | }
74 | if (stderr) {
75 | console.error(`Command error output: ${stderr}`);
76 | return res.status(500).send('Failed to upgrade Prometheus chart');
77 | }
78 | console.log(`Command output: ${stdout}`);
79 |
80 | const templateFilePath : string = filePath.replace('.yaml', '-template.yaml');
81 | fs.readFile(templateFilePath, 'utf8', (err, data) => {
82 | if (err) {
83 | console.error(err);
84 | return res.status(500).send('Failed to read template file');
85 | }
86 |
87 | fs.writeFile(filePath, data, 'utf8', (err) => {
88 | if (err) {
89 | console.error(err);
90 | return res
91 | .status(500)
92 | .send('Failed to replace file with template');
93 | }
94 | console.log('File replaced with template successfully!');
95 | return res.status(200).send('Alert created successfully');
96 | });
97 | });
98 | });
99 | });
100 | });
101 | };
102 |
103 | const alertController = {
104 | createAlert,
105 | };
106 |
107 | export default alertController;
108 |
--------------------------------------------------------------------------------
/server/controllers/alerts/HighCPUUsage-template.yaml:
--------------------------------------------------------------------------------
1 | additionalPrometheusRulesMap:
2 | custom-rules:
3 | groups:
4 | - name: Kubernetes cluster alerts
5 | rules:
6 | - alert: HighCPUUsage
7 | expr: sum(rate(container_cpu_usage_seconds_total{namespace="default"}[5m])) by (pod_name) > 1
8 | for: 5m
9 | labels:
10 | severity: warning
11 | annotations:
12 | summary: "High CPU usage in pod {{ $labels.pod_name }}"
13 | description: "The pod {{ $labels.pod_name }} in the default namespace has had high CPU usage for the past 5 minutes."
14 |
--------------------------------------------------------------------------------
/server/controllers/alerts/HighCPUUsage.yaml:
--------------------------------------------------------------------------------
1 | additionalPrometheusRulesMap:
2 | custom-rules:
3 | groups:
4 | - name: Kubernetes cluster alerts
5 | rules:
6 | - alert: MyAlert
7 | expr: sum(rate(container_cpu_usage_seconds_total{namespace="default"}[5m])) by (pod_name) > sum(rate(container_cpu_usage_seconds_total{namespace="default"}[5m])) by (pod_name) > 80
8 | for: 5m
9 | labels:
10 | severity: warning
11 | annotations:
12 | summary: "High CPU usage in pod {{ $labels.pod_name }}"
13 | description: "The pod {{ $labels.pod_name }} in the default namespace has had high CPU usage for the past 5 minutes."
14 |
--------------------------------------------------------------------------------
/server/controllers/alerts/HighMemoryUsage-template.yaml:
--------------------------------------------------------------------------------
1 | additionalPrometheusRulesMap:
2 | custom-rules:
3 | groups:
4 | - name: GroupA
5 | rules:
6 | - alert: HighMemoryUsage
7 | expr: sum(container_memory_working_set_bytes{namespace="default"}) by (pod_name) > 1e+09
8 | for: 5m
9 | labels:
10 | severity: warning
11 | annotations:
12 | summary: "High memory usage in pod {{ $labels.pod_name }}"
13 | description: "The pod {{ $labels.pod_name }} in the default namespace has used more than 1GB of memory for the past 5 minutes."
14 |
--------------------------------------------------------------------------------
/server/controllers/alerts/HighMemoryUsage.yaml:
--------------------------------------------------------------------------------
1 | additionalPrometheusRulesMap:
2 | custom-rules:
3 | groups:
4 | - name: GroupA
5 | rules:
6 | - alert: MemoryExceed3
7 | expr: sum(container_memory_working_set_bytes{namespace="default"}) by (pod_name) > 1e+09
8 | for: 5m
9 | labels:
10 | severity: warning
11 | annotations:
12 | summary: "High memory usage in pod {{ $labels.pod_name }}"
13 | description: "The pod {{ $labels.pod_name }} in the default namespace has used more than 1GB of memory for the past 5 minutes."
14 |
--------------------------------------------------------------------------------
/server/controllers/alerts/KubeAPIDown.yaml:
--------------------------------------------------------------------------------
1 | additionalPrometheusRulesMap:
2 | custom-rules:
3 | groups:
4 | - name: Kubernetes cluster alerts
5 | rules:
6 | - alert: HighCPUUsage
7 | expr: sum(rate(container_cpu_usage_seconds_total{namespace="default"}[5m])) by (pod_name) > 1
8 | for: 5m
9 | labels:
10 | severity: warning
11 | annotations:
12 | summary: "High CPU usage in pod {{ $labels.pod_name }}"
13 | description: "The pod {{ $labels.pod_name }} in the default namespace has had high CPU usage for the past 5 minutes."
14 |
15 | - alert: HighMemoryUsage
16 | expr: sum(container_memory_working_set_bytes{namespace="default"}) by (pod_name) > 1e+09
17 | for: 5m
18 | labels:
19 | severity: warning
20 | annotations:
21 | summary: "High memory usage in pod {{ $labels.pod_name }}"
22 | description: "The pod {{ $labels.pod_name }} in the default namespace has used more than 1GB of memory for the past 5 minutes."
23 |
24 | - alert: PodCrashLoopBackOff
25 | expr: rate(kube_pod_container_status_restarts_total{namespace="default",status="CrashLoopBackOff"}[5m]) > 0
26 | for: 5m
27 | labels:
28 | severity: critical
29 | annotations:
30 | summary: "Pod {{ $labels.pod_name }} is in a CrashLoopBackOff state"
31 | description: "The pod {{ $labels.pod_name }} in the default namespace has been in a CrashLoopBackOff state for the past 5 minutes."
32 |
33 | - alert: KubeAPIDown
34 | expr: up{job="kube-apiserver"} == 0
35 | for: 1m
36 | labels:
37 | severity: critical
38 | annotations:
39 | summary: "Kubernetes API server is down"
40 | description: "The Kubernetes API server is down and is not responding to requests."
41 |
42 | - alert: KubeNodeDown
43 | expr: up{job="kubelet"} == 0
44 | for: 1m
45 | labels:
46 | severity: critical
47 | annotations:
48 | summary: "Kubernetes node is down"
49 | description: "A Kubernetes node is down and is not responding to requests."
50 |
51 | - alert: KubePersistentVolumeClaimPending
52 | expr: kube_persistentvolumeclaims_pending{namespace="default"} > 0
53 | for: 5m
54 | labels:
55 | severity: warning
56 | annotations:
57 | summary: "PersistentVolumeClaim pending"
58 | description: "A PersistentVolumeClaim in the default namespace has been pending for the past 5 minutes."
59 |
--------------------------------------------------------------------------------
/server/controllers/alerts/KubeNodeDown-template.yaml:
--------------------------------------------------------------------------------
1 | additionalPrometheusRulesMap:
2 | custom-rules:
3 | groups:
4 | - name: Kubernetes cluster alerts
5 | rules:
6 | - alert: KubeNodeDown
7 | expr: up{job="kubelet"} == 0
8 | for: 1m
9 | labels:
10 | severity: critical
11 | annotations:
12 | summary: "Kubernetes node is down"
13 | description: "A Kubernetes node is down and is not responding to requests."
14 |
--------------------------------------------------------------------------------
/server/controllers/alerts/KubeNodeDown.yaml:
--------------------------------------------------------------------------------
1 | additionalPrometheusRulesMap:
2 | custom-rules:
3 | groups:
4 | - name: Kubernetes cluster alerts
5 | rules:
6 | - alert: iguh
7 | expr: up{job="kubelet"} == 0
8 | for: 1m
9 | labels:
10 | severity: critical
11 | annotations:
12 | summary: "Kubernetes node is down"
13 | description: "A Kubernetes node is down and is not responding to requests."
14 |
--------------------------------------------------------------------------------
/server/controllers/alerts/alert-rules-template.yaml:
--------------------------------------------------------------------------------
1 | additionalPrometheusRulesMap:
2 | custom-rules:
3 | groups:
4 | - name: GroupA
5 | rules:
6 | - alert: InstanceLowMemory
7 | expr: :node_memory_MemAvailable_bytes:sum < 123456789
8 | for: 1m
9 | labels:
10 | severity: critical
11 | annotations:
12 | summary: “Instance [{{ $labels.host }}] memory low”
13 | description: “{{ $labels.host }} has less than 50G memory available”
14 | - alert: InstanceDown
15 | expr: up == 0
16 | for: 1m
17 | labels:
18 | severity: critical
19 | annotations:
20 | summary: “Instance [{{ $labels.instance }}] down”
21 | description: “[{{ $labels.instance }}] of job [{{ $labels.job }}] has been down for more than 1 minute.”
--------------------------------------------------------------------------------
/server/controllers/alerts/alert-rules.yaml:
--------------------------------------------------------------------------------
1 | additionalPrometheusRulesMap:
2 | custom-rules:
3 | groups:
4 | - name: GroupA
5 | rules:
6 | - alert: InstanceLowMemory
7 | expr: :node_memory_MemAvailable_bytes:sum < 123
8 | for: 1m
9 | labels:
10 | severity: critical
11 | annotations:
12 | summary: “Instance [{{ $labels.host }}] memory low”
13 | description: “{{ $labels.host }} has less than 50G memory available”
14 | - alert: InstanceDown
15 | expr: up == 0
16 | for: 1m
17 | labels:
18 | severity: critical
19 | annotations:
20 | summary: “Instance [{{ $labels.instance }}] down”
21 | description: “[{{ $labels.instance }}] of job [{{ $labels.job }}] has been down for more than 1 minute.”
--------------------------------------------------------------------------------
/server/controllers/alerts/mergefile.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const yaml = require('js-yaml');
3 | const path = require('path');
4 |
5 | function mergeAlertYamlFiles() {
6 | // Get the full paths to the YAML files
7 | const kubeAPIDownPath = path.join(__dirname, 'KubeAPIDown.yaml');
8 | const kubeNodeDownPath = path.join(__dirname, 'KubeNodeDown.yaml');
9 |
10 | // Read in the contents of the two YAML files
11 | const kubeAPIDownYaml = fs.readFileSync(kubeAPIDownPath, 'utf8');
12 | const kubeNodeDownYaml = fs.readFileSync(kubeNodeDownPath, 'utf8');
13 |
14 | // Parse the YAML contents into objects
15 | const kubeAPIDownObj = yaml.load(kubeAPIDownYaml);
16 | const kubeNodeDownObj = yaml.load(kubeNodeDownYaml);
17 |
18 | // Merge the two objects together
19 | kubeAPIDownObj.additionalPrometheusRulesMap[
20 | 'custom-rules'
21 | ].groups[0].rules.push(kubeNodeDownObj);
22 |
23 | // Convert the merged object back to YAML
24 | const mergedYaml = yaml.dump(kubeAPIDownObj);
25 |
26 | // Write the merged YAML back to the KubeAPIDown.yaml file
27 | fs.writeFileSync(kubeAPIDownPath, mergedYaml, 'utf8');
28 |
29 | console.log('Successfully merged YAML files!');
30 | }
31 |
32 | mergeAlertYamlFiles();
33 |
--------------------------------------------------------------------------------
/server/controllers/alerts/replacefile.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const { exec } = require('child_process');
3 | const path = require('path');
4 |
5 | const req = {};
6 | req.body = { type: 'Kube', threshold: 2, name: 'Kube Node Down' };
7 | const { type, threshold, name } = req.body;
8 |
9 | let filePath;
10 | let oldText;
11 | let newText;
12 | let oldName;
13 | let command;
14 |
15 | switch (type) {
16 | case 'CPU':
17 | filePath = path.join(__dirname, '/HighCPUUsage.yaml');
18 | oldText = /1\b/g;
19 | newText = `sum(rate(container_cpu_usage_seconds_total{namespace="default"}[5m])) by (pod_name) > ${threshold}`;
20 | oldName = 'HighCPUUsage';
21 | command =
22 | 'helm upgrade --reuse-values -f alerts/HighCPUUsage.yaml prometheus prometheus-community/kube-prometheus-stack -n default';
23 | break;
24 | case 'Memory':
25 | filePath = path.join(__dirname, '/HighMemoryUsage.yaml');
26 | oldText =
27 | 'sum(container_memory_working_set_bytes{namespace="default"}) by (pod_name) > 1e+09';
28 | newText = `sum(container_memory_working_set_bytes{namespace="default"}) by (pod_name) > ${threshold}e+09`;
29 | oldName = 'HighMemoryUsage';
30 | command =
31 | 'helm upgrade --reuse-values -f alerts/HighMemoryUsage.yaml prometheus prometheus-community/kube-prometheus-stack -n default';
32 | break;
33 | default:
34 | filePath = path.join(__dirname, '/KubeNodeDown.yaml');
35 | oldText = '';
36 | newText = '';
37 | oldName = 'KubeNodeDown';
38 | command =
39 | 'helm upgrade --reuse-values -f alerts/KubeNodeDown.yaml prometheus prometheus-community/kube-prometheus-stack -n default';
40 | break;
41 | }
42 |
43 | fs.readFile(filePath, 'utf8', (err, data) => {
44 | if (err) {
45 | console.error(err);
46 | return;
47 | }
48 |
49 |
50 | let newData =
51 | type === 'Kube'
52 | ? data.replace(new RegExp(oldName, 'g'), name)
53 | : data
54 | .replace(new RegExp('1', 'g'), threshold)
55 | .replace(new RegExp(oldName, 'g'), name);
56 |
57 | fs.writeFile(filePath, newData, 'utf8', (err) => {
58 | if (err) {
59 | console.error(err);
60 | return;
61 | }
62 | console.log('File updated successfully!');
63 |
64 | exec(command, (error, stdout, stderr) => {
65 | if (error) {
66 | console.error(`Error running command: ${error.message}`);
67 | return;
68 | }
69 | if (stderr) {
70 | console.error(`Command error output: ${stderr}`);
71 | return;
72 | }
73 | console.log(`Command output: ${stdout}`);
74 | });
75 | });
76 | });
77 |
78 |
--------------------------------------------------------------------------------
/server/controllers/apiController.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction, RequestHandler } from 'express';
2 | const redis = require('../redis/redis');
3 |
4 | type Controller = {
5 | getApi?: RequestHandler;
6 | getUid?: RequestHandler;
7 | };
8 | const apiController: Controller = {};
9 |
10 | apiController.getApi = async (req, res, next) => {
11 | const cacheKey = 'api_key';
12 | try {
13 | // Try to get the API key from the cache
14 | const cachedValue = await redis.get(cacheKey);
15 | if (cachedValue !== null) {
16 | // Return the cached API key if it exists
17 | res.locals.key = cachedValue;
18 | console.log('cached retrieved', cachedValue);
19 | return next();
20 | }
21 | // If the API key is not in the cache, fetch it from the API
22 | let response = await fetch('http://localhost:3001/api/auth/keys', {
23 | method: 'POST',
24 | mode: 'no-cors',
25 | headers: {
26 | Authorization:
27 | 'Basic ' + Buffer.from('admin:prom-operator').toString('base64'),
28 | Accept: '*/*',
29 | 'Content-Type': 'application/json',
30 | },
31 | body: JSON.stringify({
32 | name: Math.random().toString(36).substring(7),
33 | role: 'Admin',
34 | secondsToLive: 86400,
35 | }),
36 | });
37 | let data = await response.json();
38 | res.locals.key = data.key;
39 | // Cache the API key for 1 hour
40 | await redis.set(cacheKey, data.key, 'EX', 3600);
41 | return next();
42 | } catch (error) {
43 | return next(error);
44 | }
45 | };
46 |
47 | apiController.getUid = async (
48 | req: Request,
49 | res: Response,
50 | next: NextFunction
51 | ) => {
52 | console.log('received uid request');
53 | console.log(req.body);
54 | const { key, dashboard }: { key: string; dashboard: string } = req.body;
55 |
56 | try {
57 | const cachedValue = await redis.get(dashboard);
58 | if (cachedValue !== null) {
59 | res.locals.uid = cachedValue;
60 | console.log('uid retrieved', cachedValue);
61 | return next();
62 | }
63 | let response = await fetch(
64 | `http://localhost:3001/api/search?query=${encodeURIComponent(dashboard)}`,
65 | {
66 | method: 'GET',
67 | headers: {
68 | Authorization: `Bearer ${key}`,
69 | 'Content-Type': 'application/json',
70 | },
71 | }
72 | );
73 | let data: any = await response.json();
74 |
75 | // Get the uid of the first dashboard in the list
76 | const uid: any = data[0].uid;
77 | res.locals.uid = uid;
78 | console.log('uid not in redis, setting uid', uid);
79 | await redis.set(dashboard, uid, 'EX', 3600);
80 |
81 | return next();
82 | } catch (error) {
83 | console.log('error fetching uid', error);
84 | return next(error);
85 | }
86 | };
87 |
88 | export default apiController;
89 |
--------------------------------------------------------------------------------
/server/controllers/clusterController.ts:
--------------------------------------------------------------------------------
1 |
2 | import {
3 | KubeConfig,
4 | AppsV1Api,
5 | CoreV1Api,
6 | NetworkingV1Api,
7 | } from '@kubernetes/client-node';
8 | import { Request, Response, NextFunction, RequestHandler } from 'express';
9 |
10 | const kc = new KubeConfig();
11 | kc.loadFromDefault();
12 |
13 | const k8sApi2: AppsV1Api = kc.makeApiClient(AppsV1Api);
14 | const k8sApi: CoreV1Api = kc.makeApiClient(CoreV1Api);
15 | const k8sApi3: NetworkingV1Api = kc.makeApiClient(NetworkingV1Api);
16 |
17 | type Controller = {
18 | getNodes?: RequestHandler;
19 | getPods?: RequestHandler;
20 | getNamespaces?: RequestHandler;
21 | getServices?: RequestHandler;
22 | getDeployments?: RequestHandler;
23 | getIngresses?: RequestHandler;
24 | getClusterInfo?:RequestHandler ;
25 | };
26 |
27 | interface Node {
28 | name: string;
29 | namespace: string;
30 | uid: string;
31 | creationTimeStamp?: any;
32 | labels: any;
33 | configSource: any;
34 | providerID: string;
35 | status: any;
36 | };
37 |
38 | interface Pod {
39 | name: string;
40 | namespace: string;
41 | uid : string;
42 | creationTimestamp: any;
43 | labels: any;
44 | containersInfo: any;
45 | nodeName : string;
46 | serviceAccount: any;
47 | containerStatuses: any;
48 | hostIP: string;
49 | podIP: string;
50 | startTime :any;
51 | }
52 |
53 | interface Test {
54 | creationTimeStamp: any;
55 | name: string;
56 | id: string;
57 | }
58 |
59 | interface Service {
60 | name: string;
61 | uid: string;
62 | creationTimeStamp: any;
63 | namespace: string;
64 | ipFamilies: any;
65 | ports: any;
66 | selector: any;
67 | type: any;
68 | }
69 |
70 | interface Deployment {
71 | name: string;
72 | uid: string;
73 | creationTimeStamp: any;
74 | namespace: string;
75 | strategy: any;
76 | matchLabels: any;
77 | replicas: any;
78 | availableReplicas: any;
79 | conditions: any;
80 | }
81 |
82 | interface ClusterInfo {
83 | nodes: Node[];
84 | pods: Pod[];
85 | namespaces: Test[];
86 | services: Service[];
87 | deployments: Deployment[];
88 | ingresses: any[];
89 | }
90 |
91 | const clusterController: Controller = {};
92 |
93 | const getNodes = async () => {
94 | const res = await k8sApi.listNode();
95 | // console.log(res.body.items[0]);
96 | const nodes: Node[] = res.body.items.map((data) => {
97 | const { name, namespace, uid, labels } = data.metadata;
98 | const creationTimeStamp: any = data.metadata.creationTimestamp;
99 | const { configSource, providerID } = data.spec;
100 | const { status } = data;
101 | const response: Node = {
102 | name,
103 | namespace,
104 | uid,
105 | creationTimeStamp,
106 | labels,
107 | configSource,
108 | providerID,
109 | status,
110 | };
111 | // console.log(response);
112 | return response;
113 | });
114 | return nodes;
115 | };
116 | // getNodes();
117 |
118 | const getPods = async () => {
119 | const res = await k8sApi.listPodForAllNamespaces();
120 | //console.log(res.body.items[0].metadata.labels);
121 | const pods: Pod[] = res.body.items.map((data) => {
122 | const { name, namespace, uid, creationTimestamp, labels } = data.metadata;
123 | const { containers, nodeName, serviceAccount } = data.spec;
124 | const { containerStatuses, hostIP, podIP, startTime } = data.status;
125 | const containersInfo = containers.map((container) => ({
126 | image: container.image,
127 | name: container.name,
128 | }));
129 | const response = {
130 | name,
131 | namespace,
132 | uid,
133 | creationTimestamp,
134 | labels,
135 | containersInfo,
136 | nodeName,
137 | serviceAccount,
138 | containerStatuses,
139 | hostIP,
140 | podIP,
141 | startTime,
142 | };
143 | return response;
144 | });
145 | return pods;
146 | };
147 | // getPods();
148 |
149 | const getNamespaces = async () => {
150 | const res = await k8sApi.listNamespace();
151 | const test: Test[] = res.body.items
152 | .filter((namespace) => namespace.metadata.name.slice(0, 4) !== 'kube')
153 | .map((namespace) => {
154 | const {creationTimeStamp} = namespace as any;
155 | return {
156 | creationTimeStamp: creationTimeStamp,
157 | name: namespace.metadata.name,
158 | id: namespace.metadata.uid,
159 | }
160 | });
161 | return test;
162 | };
163 |
164 | const getServices = async () => {
165 | const res = await k8sApi.listServiceForAllNamespaces();
166 | const response = res.body.items;
167 | // //console.log(res.body.items[0]);
168 | const results :Service[] = response.map((data) => {
169 | //console.log('service name:', data.metadata.name);
170 | const { name, uid, creationTimeStamp, namespace } = data.metadata as any;
171 | const { ipFamilies, ports, selector, type } = data.spec;
172 | const result: Service = {
173 | name,
174 | uid,
175 | creationTimeStamp,
176 | namespace,
177 | ipFamilies,
178 | ports,
179 | selector,
180 | type,
181 | };
182 | return result;
183 | });
184 | // console.log(results);
185 | return results;
186 | };
187 | // getServices();
188 |
189 | const getDeployments = async () => {
190 | const res = await k8sApi2.listDeploymentForAllNamespaces();
191 | const response = res.body.items;
192 | //console.log(response[0].spec.selector.matchLabels);
193 | const results: Deployment[] = response.map((data) => {
194 | // console.log(data.spec.template);
195 | const { name, uid, creationTimeStamp, namespace } = data.metadata as any;
196 | const { strategy, replicas, selector: matchLabels } = data.spec;
197 | const { availableReplicas, conditions } = data.status;
198 | const result: Deployment = {
199 | name,
200 | uid,
201 | creationTimeStamp,
202 | namespace,
203 | strategy,
204 | matchLabels,
205 | replicas,
206 | availableReplicas,
207 | conditions,
208 | };
209 | return result;
210 | });
211 | // console.log(results);
212 | return results;
213 | };
214 |
215 | // getDeployments();
216 |
217 | const getIngresses = async () => {
218 | const res = await k8sApi3.listIngressForAllNamespaces();
219 | return res.body.items;
220 | };
221 |
222 | clusterController.getClusterInfo = async (req, res, next) => {
223 | try {
224 | const nodes = await getNodes();
225 | const pods = await getPods();
226 | const namespaces = await getNamespaces();
227 | const services = await getServices();
228 | const deployments = await getDeployments();
229 | const ingresses = await getIngresses();
230 | const clusterInfo:ClusterInfo = {
231 | nodes,
232 | pods,
233 | namespaces,
234 | services,
235 | deployments,
236 | ingresses,
237 | };
238 | //console.log('server side nodes', clusterInfo.nodes);
239 | res.locals.clusterInfo = clusterInfo;
240 | return next();
241 | } catch (err) {
242 | return next({
243 | log: `clusterController.getNamespaces ERROR: ${err}`,
244 | status: 500,
245 | message: { err: 'An error occurred in getClusterInfo' },
246 | });
247 | }
248 | };
249 |
250 |
251 | export default clusterController;
252 |
--------------------------------------------------------------------------------
/server/controllers/setupController.ts:
--------------------------------------------------------------------------------
1 | import { exec, execSync, spawn, spawnSync } from 'child_process';
2 | import { Request, Response, NextFunction, RequestHandler } from 'express';
3 | const Redis = require('ioredis');
4 | import redis from '../redis/redis';
5 |
6 | type Controller = {
7 | promInit?: RequestHandler;
8 | grafEmbed?: RequestHandler;
9 | forwardPorts?: RequestHandler;
10 | forwardProm?: RequestHandler;
11 | redisInit?: RequestHandler;
12 | };
13 | const setupController: Controller = {};
14 |
15 | // synchronous child processes used here because these commands must execute successively
16 | setupController.promInit = (
17 | req: Request,
18 | res: Response,
19 | next: NextFunction
20 | ) => {
21 | console.log('\n\nPrometheus Setup Starting\n\n');
22 |
23 | spawnSync(
24 | 'helm repo add prometheus-community https://prometheus-community.github.io/helm-charts',
25 | {
26 | stdio: 'inherit',
27 | shell: true,
28 | }
29 | );
30 | spawnSync('helm repo update', {
31 | stdio: 'inherit',
32 | shell: true,
33 | });
34 | spawnSync(
35 | 'helm install prometheus prometheus-community/kube-prometheus-stack',
36 | {
37 | stdio: 'inherit',
38 | shell: true,
39 | }
40 | );
41 | return next();
42 | };
43 |
44 | // need to use kubectl to find unique grafana pod name, apply maniefests, then restart pod in order to have manifest rules take effect
45 | setupController.grafEmbed = (
46 | req: Request,
47 | res: Response,
48 | next: NextFunction
49 | ) => {
50 | console.log('\n\nGrafana Setup Starting\n\n');
51 | let podName: any;
52 | const getter = exec('kubectl get pods', (err, stdout, stderr) => {
53 | if (err) {
54 | console.error(`exec error: ${err}`);
55 | return;
56 | }
57 | if (stderr) {
58 | console.log(`stderr: ${stderr}`);
59 | return;
60 | }
61 | const output = stdout.split('\n');
62 | output.forEach((line) => {
63 | if (line.includes('prometheus-grafana')) {
64 | [podName] = line.split(' ');
65 | }
66 | });
67 |
68 | console.log(podName);
69 | });
70 |
71 | getter.once('close', () => {
72 | spawnSync('kubectl apply -f prometheus-grafana.yaml', {
73 | stdio: 'inherit',
74 | shell: true,
75 | });
76 | spawnSync(`kubectl delete pod ${podName}`, {
77 | stdio: 'inherit',
78 | shell: true,
79 | });
80 | // setupController.forwardPort();
81 | return next();
82 | });
83 | };
84 |
85 | // while loop checks to ensure kubernetes pod is ready otherwise port forwarding will fail
86 | setupController.forwardPorts = (
87 | req: Request,
88 | res: Response,
89 | next: NextFunction
90 | ) => {
91 | console.log('\n\nForwarding Ports\n\n');
92 | let grafPod: string, promPod: string, alertPod: string;
93 | let podStatus;
94 | while (podStatus !== 'Running') {
95 | const abc = execSync('kubectl get pods');
96 | abc
97 | .toString()
98 | .split('\n')
99 | .forEach((line) => {
100 | if (!promPod && line.includes('prometheus-0'))
101 | [promPod] = line.split(' ');
102 | if (!alertPod && line.includes('alertmanager-0'))
103 | [alertPod] = line.split(' ');
104 | if (line.includes('prometheus-grafana')) {
105 | if (line.includes('Running')) podStatus = 'Running';
106 | [grafPod] = line.split(' ');
107 | }
108 | console.log('grafana pod:', grafPod);
109 | });
110 | }
111 |
112 | const ports = spawn(
113 | `kubectl port-forward ${grafPod} 3001:3000 & kubectl port-forward ${promPod} 9090 & kubectl port-forward ${alertPod} 9093`,
114 | { shell: true }
115 | );
116 | ports.stdout.on('data', (data) => {
117 | console.log(`stdout: ${data}`);
118 | });
119 | ports.stderr.on('data', (data) => {
120 | console.error(`grafana port forwarding error: ${data}`);
121 | });
122 | return next();
123 | };
124 |
125 | setupController.redisInit = (
126 | req: Request,
127 | res: Response,
128 | next: NextFunction
129 | ) => {
130 | redis.flushall((err:Error, result:string) => {
131 | if (err) {
132 | console.error(err);
133 | } else {
134 | console.log(result); // Output: OK
135 | }
136 | });
137 | return next();
138 | };
139 |
140 | export default setupController;
141 |
--------------------------------------------------------------------------------
/server/redis/redis.js:
--------------------------------------------------------------------------------
1 | const Redis = require('ioredis');
2 |
3 | const redis = new Redis({
4 | host: 'localhost',
5 | port: 6379,
6 | });
7 |
8 | module.exports = redis;
--------------------------------------------------------------------------------
/server/routes/alerts.ts:
--------------------------------------------------------------------------------
1 | import express, { Request, Response, Router } from 'express';
2 | import alertController from '../controllers/alertController';
3 |
4 | const router: Router = express.Router();
5 |
6 | router.post('/', alertController.createAlert, (req:Request, res:Response):void => {
7 | res.sendStatus(200);
8 | });
9 |
10 | export default router;
11 |
--------------------------------------------------------------------------------
/server/routes/cluster.ts:
--------------------------------------------------------------------------------
1 | import express, { Router, Request, Response } from 'express';
2 | import clusterController from '../controllers/clusterController';
3 |
4 | const router: Router = express.Router();
5 |
6 | router.get(
7 | '/',
8 | clusterController.getClusterInfo,
9 | (req: Request, res: Response):void => {
10 | res.status(200).json(res.locals.clusterInfo);
11 | }
12 | );
13 |
14 | export default router;
15 |
--------------------------------------------------------------------------------
/server/routes/grafana.ts:
--------------------------------------------------------------------------------
1 | import express, { Router, Request, Response } from 'express';
2 | import apiController from '../controllers/apiController';
3 |
4 | const router: Router = express.Router();
5 |
6 |
7 |
8 | router.get('/key', apiController.getApi, (req: Request, res: Response):void => {
9 | res.status(200).json(res.locals.key);
10 | });
11 | router.post('/uid', apiController.getUid, (req: Request, res: Response):void => {
12 | res.status(200).json(res.locals.uid);
13 | });
14 |
15 | export default router;
16 |
--------------------------------------------------------------------------------
/server/routes/setup.ts:
--------------------------------------------------------------------------------
1 | import express, { Router, Request, Response } from 'express';
2 | import setupController from '../controllers/setupController';
3 |
4 | const router: Router = express.Router();
5 |
6 | router.get(
7 | '/promSetup',
8 | setupController.promInit,
9 | (req: Request, res: Response): void => {
10 | res.sendStatus(200);
11 | }
12 | );
13 | router.get(
14 | '/grafSetup',
15 | setupController.grafEmbed, setupController.redisInit,
16 | (req: Request, res: Response): void => {
17 | res.sendStatus(200);
18 | }
19 | );
20 | router.get(
21 | '/forwardPorts',
22 | setupController.redisInit, setupController.forwardPorts,
23 | (req: Request, res: Response): void => {
24 | res.sendStatus(200);
25 | }
26 | );
27 |
28 | export default router;
29 |
--------------------------------------------------------------------------------
/server/server.ts:
--------------------------------------------------------------------------------
1 | import express, { Application, Request, Response, NextFunction } from 'express';
2 | import cors from 'cors';
3 | import clusterRouter from './routes/cluster';
4 | import grafanaRouter from './routes/grafana';
5 | import setupRouter from './routes/setup';
6 | import alertsRouter from './routes/alerts';
7 | import * as path from 'path';
8 |
9 | //Define the error object type to use
10 | type ServerError= {
11 | log: string;
12 | status: number;
13 | message: { err: string };
14 | }
15 |
16 |
17 |
18 | const app: Application = express();
19 | const PORT: number = 3000;
20 |
21 | app.use(express.json());
22 | app.use(cors());
23 | // console.log('type: ', process.env.NODE_ENV);
24 | if (process.env.NODE_ENV === 'production') {
25 | // console.log('i am inside the production mode fetching files...')
26 | // statically serve everything in the build folder on the route '/build'
27 | app.use('/', express.static(path.join(__dirname, '../build')));
28 | // serve index.html on the route '/'
29 | // app.get('/', (req, res) => {
30 | // return res.status(200).sendFile(path.join(__dirname, '../build/index.html'));
31 | // });
32 | }
33 |
34 | app.use('/setup', setupRouter);
35 | app.use('/clusterdata', clusterRouter);
36 | app.use('/grafana', grafanaRouter);
37 |
38 | app.use('/alerts', alertsRouter);
39 |
40 | // catch all
41 | app.use((req: Request, res: Response):Response => res.sendStatus(404));
42 |
43 | // global err handler
44 | app.use((err: ServerError, req: Request, res: Response, next: NextFunction):Response => {
45 | const defaultErr: ServerError = {
46 | log: 'Express error handler caught unknown middleware error',
47 | status: 400,
48 | message: { err: 'An error occurred' },
49 | };
50 | const errorObj = { defaultErr, ...err };
51 | console.log(errorObj.log);
52 | return res.status(errorObj.status).json(errorObj.message);
53 | });
54 |
55 | app.listen(PORT.toString(), () =>
56 | console.log(`Server listening on port ${PORT}`)
57 | );
58 |
59 | export default app;
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./build/",
4 | "noImplicitAny": true,
5 | "module": "commonJS",
6 | "esModuleInterop": true,
7 | "target": "ES2016",
8 | "jsx": "react",
9 | "allowJs": true,
10 | "moduleResolution": "node",
11 | "removeComments": true
12 | },
13 | "include": ["client/**/*", "server/**/*", "./custom.d.ts"],
14 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | module.exports = {
5 | entry: './client/index.tsx',
6 | output: {
7 | publicPath: '/',
8 | path: path.resolve(__dirname, 'build/'),
9 | filename: 'bundle.js',
10 | },
11 | mode: 'development',
12 | module: {
13 | rules: [
14 | {
15 | // checks the extension
16 | test: /.(js|jsx)$/,
17 | // ignores these folders/directories
18 | exclude: /node_modules/,
19 | // if test passes, webpack uses the loaders specified
20 | use: {
21 | loader: 'babel-loader',
22 | options: {
23 | presets: ['@babel/preset-env', '@babel/preset-react'],
24 | plugins: ['@babel/plugin-syntax-jsx'],
25 | },
26 | },
27 | },
28 | {
29 | test: /\.tsx?$/,
30 | use: {
31 | loader: 'babel-loader',
32 | options: {
33 | presets: [
34 | '@babel/preset-env',
35 | '@babel/preset-react',
36 | '@babel/preset-typescript',
37 | ],
38 | plugins: ['@babel/plugin-transform-react-jsx'],
39 | },
40 | },
41 | exclude: /node_modules/,
42 | },
43 | {
44 | test: /\.css$/,
45 | use: ['style-loader', 'css-loader'],
46 | },
47 | {
48 | test: /\.(scss)$/,
49 | use: [
50 | {
51 | loader: 'style-loader',
52 | },
53 | {
54 | loader: 'css-loader',
55 | },
56 | {
57 | loader: 'sass-loader',
58 | },
59 | ],
60 | },
61 | {
62 | test: /\.(png|jpe?g|gif|svg)$/i,
63 | use: 'file-loader',
64 | },
65 | {
66 | test: /\.html$/i,
67 | loader: 'html-loader',
68 | },
69 | ],
70 | },
71 | devServer: {
72 | // contentBase: path.join(__dirname, "client/"),
73 | compress: true,
74 | host: 'localhost',
75 | port: '8080',
76 | historyApiFallback: true,
77 | proxy: [{ '/': { target: 'http://localhost:3000', secure: false } }],
78 | },
79 | // set plugins
80 | plugins: [
81 | new HtmlWebpackPlugin({
82 | template: 'index.html',
83 | // favicon: 'test.png'
84 | }),
85 | ],
86 | resolve: { extensions: ['*', '.js', '.jsx', '.tsx', '.ts'] },
87 | };
88 |
--------------------------------------------------------------------------------