├── .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 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 76 | 77 | pod 88 | 91 | 98 | 105 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /build/352bfcc759fedc27fafc6761c5d12463.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 76 | 77 | ns 88 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /build/6eeb75c8b4bdf45e996a.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ClusterWatch/63a5d7c5dfbc2fc1539746dbdaa045e0a43974bf/build/6eeb75c8b4bdf45e996a.ico -------------------------------------------------------------------------------- /build/8fe4af813952d3d8c7285fde6e71c874.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 76 | 77 | svc 88 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 127 | 128 | 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 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 76 | 77 | deploy 88 | 91 | 96 | 101 | 102 | 103 | 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 |
83 |
84 | {' '} 85 | 92 | 93 |
94 | 95 |
96 | 103 | 104 |
105 | 106 |
107 | 114 | 115 |
116 | 117 | 118 |
119 |
120 | )} 121 | {submittedAlertOption === 'Memory' && submittedMemory === '' && ( 122 |
123 |

Memory Threshold

124 |
125 | 128 | 134 | 135 | 136 |
137 |
138 | )} 139 | 140 | {(submittedMemory !== '' || 141 | submittedCPU !== '' || 142 | submittedAlertOption === 'Kube') && ( 143 |
144 |

Create Alert Name

145 |
146 | 147 | 153 | 154 |
155 | 160 |
161 |
162 | )} 163 | 164 | {submittedAlertOption === 'CPU' && submittedCPU === '' && ( 165 |
166 |

CPU Threshold

167 |
168 | 171 | 178 |
179 | 180 |
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 |
10 | 11 | 12 |
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 | My Logo 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 | 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 |
23 | 24 | 25 | 26 |
27 | ); 28 | } 29 | 30 | export default Dashboard; 31 | -------------------------------------------------------------------------------- /client/components/visualizer/icons/deployment-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 76 | 77 | deploy 88 | 91 | 96 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /client/components/visualizer/icons/namespace-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 76 | 77 | ns 88 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /client/components/visualizer/icons/pod-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 76 | 77 | pod 88 | 91 | 98 | 105 | 112 | 113 | 114 | 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 | --------------------------------------------------------------------------------