├── .babelrc
├── .gitignore
├── README.md
├── __tests__
├── __mocks__
│ └── WineSwirl.gif
├── installation.test.js
├── loginPage.test.js
├── server.test.js
└── signup.test.js
├── client
├── App.jsx
├── index.js
├── src
│ ├── assets
│ │ ├── WineGlass.module.scss
│ │ ├── WineGlass.tsx
│ │ ├── WineSwirl.gif
│ │ └── logo.png
│ ├── components
│ │ └── RootLayout.jsx
│ ├── pages
│ │ ├── Dashboard.tsx
│ │ ├── Installation.module.scss
│ │ ├── Installation.tsx
│ │ ├── LoginPage.tsx
│ │ └── SignupPage.tsx
│ ├── redux
│ │ ├── slices
│ │ │ └── userSlice.js
│ │ └── store.js
│ └── styles
│ │ ├── Dashboard.module.scss
│ │ ├── Home.module.scss
│ │ ├── RootLayout.module.scss
│ │ └── _colors.scss
└── types.d.ts
├── mocks
├── WineSwirl.mock.js
├── handlers.js
└── server.js
├── package.json
├── prometheus-grafana.yaml
├── public
├── index.html
└── readme
│ ├── K-Github-Logo.png
│ ├── dashboard.gif
│ ├── getstarted.gif
│ ├── github_icon.png
│ ├── linkedin_icon.png
│ ├── loading.gif
│ └── tests.png
├── server
├── controllers
│ ├── cookieController.ts
│ ├── grafanaController.ts
│ ├── installController.ts
│ ├── sessionController.ts
│ └── userController.ts
├── models
│ ├── UserModel.ts
│ └── sessionModel.ts
├── panels
│ └── KubernetFullDash.json
├── routes
│ ├── deleteRoute.ts
│ ├── loginRoute.ts
│ └── signUpRoute.ts
└── server.ts
├── test-setup.js
├── tsconfig.json
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/preset-typescript",
5 | "@babel/preset-react"
6 | ]
7 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | .env
4 | dist
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ---
4 |
5 | 
6 | 
7 | 
8 | 
9 | 
10 | 
11 | 
12 | 
13 | 
14 | 
15 | 
16 | 
17 |
18 | [](https://medium.com/@teamkubernet)
19 | 
20 |
21 |
22 |
23 |
24 |
25 |
26 | • [Introduction](#introduction)• [Getting started](#getting-started) • [Installation](#installation) • [Considerations](#considerations) • [Open Source](#open-source) •[Meet The Team](#meet-the-team) •
27 |
28 |
29 |
30 | ## Introduction
31 |
32 | Kubernét is a user-friendly solution for effortlessly visualizing and monitoring your Kubernetes metrics in real-time. No coding or technical expertise needed! Through the intuitive interface, Kubernét simplifies the complex task of tracking and analyzing your K8s cluster metrics.
33 |
34 | Before using our application, be sure to have your kubernetes cluster running and have installed helm.
35 |
36 | - If you have not installed helm before, install helm from [here](https://helm.sh/docs/intro/install/).
37 |
38 | ## Getting Started
39 |
40 | To get started with opening the web application, first pull this repo onto your machine. In order to have the application work, you will need to create an .env file to store your mongo-URI. within your .env file please type the following:
41 |
42 | ```js
43 | MONGO_URI = '';
44 | ```
45 |
46 | Once that file is created, open the terminal and install all the packages with
47 |
48 | ```bash
49 | npm install
50 | ```
51 |
52 | then type in
53 |
54 | ```bash
55 | npm start
56 | ```
57 |
58 | On your browser, localhost:8080 should appear, and you are now running our application!
59 |
60 | If this is your first time using the application hit the **Get Started** button. If you have an account with the application already, hit the _already have an account_ button.
61 |
62 |
63 |
64 | _Once you type in your username and password, the application will direct you to login. Make sure the username you are inputting is unique!_
65 |
66 | ## Installation
67 |
68 | After typing in your credentials, our application will begin the process of install Prometheus and Grafana onto your Kubernetes cluster through helm and apply our custom .yaml files.
69 |
70 |
71 |
72 | The dashboard will be saved onto your profile and be displayed on the application.
73 |
74 | Your cluster metrics are now visible in real-time and update automatically! Feel free to move around the dashboard and customize the panel layout!
75 |
76 |
77 | ## Considerations
78 |
79 | The application requires several default port configurations, so be sure not to populate these ports!
80 |
81 | | Port | Application |
82 | | :--: | :---------: |
83 | | 9090 | Prometheus |
84 | | 8080 | Kubernét |
85 | | 3000 | Grafana |
86 | | 5050 | Express |
87 |
88 | ## Open Source
89 |
90 | As this is a free open source product, we would love having your feedback through opening issues, and contributions. If you would like to contribute to this open source project,
91 |
92 | 1. Clone our repo and create a new branch:
93 |
94 | ```bash
95 | git checkout -b
96 | ```
97 |
98 | 2. Make changes, and run test
99 |
100 | ```bash
101 | npm run test
102 | ```
103 |
104 | You should see this:
105 |
106 |
107 |
108 | Be sure that your server is running at the time in order for the back-end tests to pass.
109 |
110 | 3. Submit a pull request with clear descriptions of changes made. One of our team members will look at it as soon as possible to approve your PR.
111 |
112 | | Features | Status |
113 | | :------------------------------------- | :----: |
114 | | Automatic Prometheus Integration | ✅ |
115 | | Automatic Grafana Integration | ✅ |
116 | | Custom Grafana .yaml installation | ✅ |
117 | | Custom Dashboard Creation | ✅ |
118 | | User Data Encryption | ✅ |
119 | | Implement RTL + Jest front-end testing | ✅ |
120 | | Implement Supertest back-end testing | ✅ |
121 | | Full Typescript conversion | ✅ |
122 | | Standalone application | ⏳ |
123 | | Alerting System | 🙏🏻 |
124 | | Individual node health visualizer | 🙏🏻 |
125 | | Cluster visualization | 🙏🏻 |
126 |
127 | - ✅ = **Ready**
128 | - ⏳ = In progress
129 | - 🙏🏻 = _Looking for contributors_
130 |
131 | ## Meet The Team
132 |
133 |
141 |
--------------------------------------------------------------------------------
/__tests__/__mocks__/WineSwirl.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Kubernet/0479184ee5e94f6b8100651a843b61edaf2103a8/__tests__/__mocks__/WineSwirl.gif
--------------------------------------------------------------------------------
/__tests__/installation.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '@testing-library/jest-dom';
3 | import { fireEvent, getByRole, getByText, render, screen, waitFor} from "@testing-library/react";
4 | import '@testing-library/jest-dom/extend-expect';
5 | import { BrowserRouter as Router, createMemoryRouter, createRoutesFromElements, Route, Routes, RouterProvider } from 'react-router-dom';
6 | import userEvent from '@testing-library/user-event';
7 |
8 |
9 |
10 | import { server } from '../mocks/server';
11 |
12 | // import our components
13 | import LoginPage from '../client/src/pages/LoginPage';
14 | import Dashboard from '../client/src/pages/Dashboard';
15 | import SignupPage from '../client/src/pages/SignupPage';
16 | import Installation from '../client/src/pages/Installation';
17 | jest.mock('./WineSwirl.gif', () => './__mocks__/WineSwirl.gif');
18 |
19 |
20 | beforeAll(() => {
21 | server.listen();
22 | });
23 |
24 | afterEach(() => {
25 | server.resetHandlers();
26 | });
27 |
28 | afterAll(() => {
29 | server.close();
30 | });
31 |
32 |
33 | // returns an array of objects where each object is a route
34 | const appRoutes = createRoutesFromElements(
35 | <>
36 | } />
37 | } />
38 | } />
39 | } />
40 | >
41 | );
42 |
43 |
44 | describe('Installation', () => {
45 | describe('Rendering', () => {
46 | beforeEach(() => {
47 | const memoryRouter = createMemoryRouter(appRoutes, {initialEntries: ['/']});
48 |
49 | render(
50 |
51 | )
52 | })
53 | test('Installation page displays the Get Started Button', () => {
54 | const getStartedButton = screen.getByRole('button', {name: 'Get Started'});
55 |
56 | expect(getStartedButton).toBeInTheDocument();
57 | })
58 |
59 | test("Displays button 'already have an account?'", () => {
60 | const alreadyHaveAnAccountButton = screen.getByText('already have an account?')
61 | expect(alreadyHaveAnAccountButton).toBeInTheDocument();
62 | })
63 | })
64 |
65 | describe('Navigation', () => {
66 | beforeEach(() => {
67 | const memoryRouter = createMemoryRouter(appRoutes, {initialEntries: ['/']});
68 | render(
69 |
70 | )
71 | })
72 |
73 | test("User is navigated to sign up page when they click on 'Get Started' button", async () => {
74 | const getStartedBtn = screen.getByRole('button', {name: "Get Started"});
75 |
76 | expect(screen.queryByText('Get Started')).toBeInTheDocument();
77 | expect(screen.queryByText('Create Account')).not.toBeInTheDocument();
78 |
79 | await userEvent.click(getStartedBtn);
80 | await waitFor(() => {screen.findByText('Create Account')});
81 |
82 | expect(screen.getByText('Create Account')).toBeInTheDocument();
83 | } )
84 |
85 | test("User is routed to login page when they click on 'already have an account?' button", async () => {
86 |
87 | const hasAnAccountBtn = screen.getByRole('button', {name: 'already have an account?'})
88 |
89 | expect(screen.queryByText('Login')).not.toBeInTheDocument();
90 |
91 | await userEvent.click(hasAnAccountBtn);
92 | await waitFor(() => {screen.findByText('Login')});
93 | expect(screen.getByText('Login')).toBeInTheDocument();
94 | })
95 | })
96 |
97 |
98 | });
99 |
100 |
--------------------------------------------------------------------------------
/__tests__/loginPage.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '@testing-library/jest-dom';
3 | import { fireEvent, getByRole, getByText, render, screen, waitFor} from "@testing-library/react";
4 | import '@testing-library/jest-dom/extend-expect';
5 | import { BrowserRouter as Router, createMemoryRouter, createRoutesFromElements, Route, Routes, RouterProvider } from 'react-router-dom';
6 | import userEvent from '@testing-library/user-event';
7 |
8 | import { server } from '../mocks/server';
9 |
10 | import LoginPage from '../client/src/pages/LoginPage';
11 | import Dashboard from '../client/src/pages/Dashboard';
12 | import SignupPage from '../client/src/pages/SignupPage';
13 | import Installation from '../client/src/pages/Installation';
14 | jest.mock('./WineSwirl.gif', () => './__mocks__/WineSwirl.gif');
15 |
16 | beforeAll(() => {
17 | server.listen();
18 | });
19 |
20 | afterEach(() => {
21 | server.resetHandlers();
22 | });
23 |
24 | afterAll(() => {
25 | server.close();
26 | });
27 |
28 | // returns an array of objects where each object is a route
29 | const appRoutes = createRoutesFromElements(
30 | <>
31 | } />
32 | } />
33 | } />
34 | } />
35 | >
36 | );
37 |
38 |
39 |
40 | describe('LoginPage', () => {
41 |
42 | describe('Rendering', () => {
43 | beforeEach(() => {
44 | const memoryRouter = createMemoryRouter(appRoutes, {initialEntries: ['/loginPage']});
45 |
46 | render(
47 |
48 | )
49 | })
50 | test('Checks if Username input field is on the page', () => {
51 | expect(screen.getByPlaceholderText('Username')).toBeInTheDocument();
52 | });
53 |
54 | test('Checks if Password input field is on the page', () => {
55 | expect(screen.getByPlaceholderText('Password')).toBeInTheDocument();
56 | });
57 |
58 | test('Checks if Login button is on the page', () => {
59 | const loginButton = screen.getByRole('button', {name:'Login'});
60 | expect(loginButton).toBeInTheDocument();
61 | });
62 |
63 | test("Checks if 'create an account' button is on the page", () => {
64 | const createAnAccount = screen.getByRole('button', {name: 'create an account'});
65 | expect(createAnAccount).toBeInTheDocument();
66 | });
67 | });
68 |
69 | describe('Behavior', () => {
70 | beforeEach(() => {
71 | const memoryRouter = createMemoryRouter(appRoutes, {initialEntries: ['/loginPage']});
72 |
73 | render(
74 |
75 | )
76 | })
77 | test('User types in login details and is navigated to dashboard',async () => {
78 | const loginBtn = screen.getByRole('button', {name: 'Login'});
79 | const userNameInput = screen.getByPlaceholderText('Username');
80 | const passwordInput = screen.getByPlaceholderText('Password');
81 |
82 | await userEvent.type(userNameInput, 'james');
83 | await userEvent.type(passwordInput, 'james123');
84 |
85 | expect(userNameInput.value).toBe('james');
86 | expect(passwordInput.value).toBe('james123');
87 |
88 | await userEvent.click(loginBtn);
89 |
90 | waitFor(async() => {
91 | await screen.findByTitle('myIframe', {})
92 | expect(screen.findByTitle('myIframe')).toBeInTheDocument();
93 | })
94 |
95 |
96 | })
97 |
98 |
99 |
100 |
101 |
102 | })
103 |
104 |
105 |
106 | })
--------------------------------------------------------------------------------
/__tests__/server.test.js:
--------------------------------------------------------------------------------
1 | const request = require('supertest');
2 | const server = 'http://localhost:5050';
3 | const testUsername = `test${Date.now()}`;
4 |
5 | beforeAll(() => {
6 | return request(server)
7 | .post('/signup')
8 | .send({ username: testUsername, password: '12345' });
9 | });
10 |
11 | afterAll(() => {
12 | return request(server)
13 | .post('/delete')
14 | .send({ username: testUsername, password: '12345' });
15 | });
16 |
17 | describe('Backend Tests', () => {
18 | describe('Signup Route', () => {
19 | it('Should return 400 if username already exists', () => {
20 | return request(server)
21 | .post('/signup')
22 | .send({ username: testUsername, password: '12345' })
23 | .expect(400)
24 | .expect('Content-Type', /application\/json/);
25 | });
26 | it('Should invoke 401 with invalid signup', () => {
27 | return request(server)
28 | .post('/signup')
29 | .send({ username: 'test' })
30 | .expect(400)
31 | .expect('Content-Type', /application\/json/);
32 | });
33 | });
34 |
35 | describe('Login Route', () => {
36 | it('Should successfully login to a test account', () => {
37 | return request(server)
38 | .post('/login')
39 | .send({ username: testUsername, password: '12345' })
40 | .expect(201)
41 | .expect('Content-Type', /application\/json/);
42 | }, 25000);
43 | it('should invoke 401 with wrong user credentials', () => {
44 | return request(server)
45 | .post('/login')
46 | .send({ username: 'test' })
47 | .expect(401)
48 | .expect('Content-Type', /application\/json/);
49 | });
50 | });
51 |
52 | describe('Invalid endpoints', () => {
53 | it('Should return 404 Page not found if user queries an invalid endpoint', () => {
54 | return request(server)
55 | .get('/invalid')
56 | .expect(404)
57 | .expect('Content-Type', /text\/html/);
58 | });
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/__tests__/signup.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen, waitFor } from '@testing-library/react';
3 | import {
4 | createRoutesFromElements,
5 | Route,
6 | createMemoryRouter,
7 | RouterProvider,
8 | } from 'react-router-dom';
9 | import '@testing-library/jest-dom';
10 | import userEvent from '@testing-library/user-event';
11 | import { server } from '../mocks/server';
12 |
13 | //imported pages
14 | import LoginPage from '../client/src/pages/LoginPage';
15 | import Dashboard from '../client/src/pages/Dashboard';
16 | import SignupPage from '../client/src/pages/SignupPage';
17 | import Installation from '../client/src/pages/Installation.tsx';
18 |
19 | const theRoutes = createRoutesFromElements(
20 | <>
21 | } />
22 | } />
23 | } />
24 | } />
25 | >
26 | );
27 |
28 | describe('Signup Page', () => {
29 | // Establish API mocking before all tests.
30 | beforeAll(() => server.listen());
31 |
32 | // Reset any request handlers that we may add during the tests,
33 | // so they don't affect other tests.
34 | afterEach(() => server.resetHandlers());
35 |
36 | // Clean up after the tests are finished.
37 | afterAll(() => server.close());
38 |
39 | describe('Rendering', () => {
40 | beforeEach(() => {
41 | const router = createMemoryRouter(theRoutes, {
42 | initialEntries: ['/signupPage'],
43 | });
44 | render( );
45 | });
46 |
47 | test('Signup page loads with Username input field', async () => {
48 | expect(screen.getByPlaceholderText('Username')).toBeInTheDocument();
49 | });
50 |
51 | test('Signup page loads with Password input field', async () => {
52 | expect(screen.getByPlaceholderText('Password')).toBeInTheDocument();
53 | });
54 |
55 | test('Signup page loads with "Create Account" button', async () => {
56 | expect(screen.getByText('Create Account')).toBeInTheDocument();
57 | });
58 |
59 | test('Signup page loads with "already have an account?" button', async () => {
60 | expect(screen.getByText('already have an account?')).toBeInTheDocument();
61 | });
62 | });
63 |
64 | describe('User Actions', () => {
65 | test('Clicking "already have an account?" should navigate to login page', async () => {
66 | const router = createMemoryRouter(theRoutes, {
67 | initialEntries: ['/signupPage'],
68 | });
69 | render( );
70 | expect(screen.queryByText('Login')).not.toBeInTheDocument();
71 | await userEvent.click(screen.getByText('already have an account?'));
72 | expect(screen.getByText('Login')).toBeInTheDocument();
73 | });
74 |
75 | test('Should enter username and password and click on login button', async () => {
76 | const router = createMemoryRouter(theRoutes, {
77 | initialEntries: ['/signupPage'],
78 | });
79 | render( );
80 |
81 | const usernameInput = screen.getByPlaceholderText('Username');
82 | const passwordInput = screen.getByPlaceholderText('Password');
83 | const createAccountButton = screen.getByRole('button', {
84 | name: 'Create Account',
85 | });
86 |
87 | await userEvent.type(usernameInput, 'james');
88 | await userEvent.type(passwordInput, 'james123');
89 |
90 | expect(usernameInput.value).toBe('james');
91 | expect(passwordInput.value).toBe('james123');
92 |
93 | expect(screen.queryByTestId('myIframe')).not.toBeInTheDocument();
94 | await userEvent.click(createAccountButton);
95 | // await screen.findByRole('iframe');
96 | waitFor(async () => {
97 | await screen.findByTitle('myIframe', {});
98 | expect(screen.findByTitle('myIframe', {})).toBeInTheDocument();
99 | });
100 | });
101 | });
102 | });
103 |
--------------------------------------------------------------------------------
/client/App.jsx:
--------------------------------------------------------------------------------
1 | // Dependancies
2 | import React from 'react';
3 | import {
4 | createBrowserRouter,
5 | Route,
6 | createRoutesFromElements,
7 | RouterProvider,
8 | } from 'react-router-dom';
9 |
10 | // Children Components
11 | import LoginPage from './src/pages/LoginPage';
12 | import Dashboard from './src/pages/Dashboard';
13 | import SignupPage from './src/pages/SignupPage';
14 | import Installation from './src/pages/Installation.tsx';
15 | import RootLayout from './src/components/RootLayout';
16 |
17 | // Dynamic rendering of components
18 |
19 | const router = createBrowserRouter(
20 | createRoutesFromElements(
21 | }>
22 | } />
23 | } />
24 | } />
25 | } />
26 |
27 | )
28 | );
29 |
30 | function App() {
31 | return ;
32 | }
33 |
34 | export default App;
35 |
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App.jsx';
4 | import { store } from './src/redux/store.js';
5 | import { Provider } from 'react-redux';
6 |
7 |
8 |
9 |
10 | const container = document.getElementById('root');
11 | const root = ReactDOM.createRoot(container);
12 | root.render(
13 |
14 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/client/src/assets/WineGlass.module.scss:
--------------------------------------------------------------------------------
1 | @import '../styles/colors';
2 | .container {
3 | height: 90%;
4 | margin: 10px;
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | display: flex;
9 | flex-direction: column;
10 | }
11 |
12 | .animation {
13 | color: $color3;
14 | opacity: 0;
15 | animation: fade-in 1s ease-in-out forwards;
16 | }
17 |
18 | @keyframes fade-in {
19 | 0% {
20 | opacity: 0;
21 | }
22 | 100% {
23 | opacity: 1;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/client/src/assets/WineGlass.tsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React, { useState, useEffect } from 'react';
3 |
4 | // Styles
5 | import styles from './WineGlass.module.scss';
6 |
7 | // Assets
8 | import WineSwirl from './WineSwirl.gif';
9 |
10 | function WineGlass(): JSX.Element {
11 | const messageArray: string[] = [
12 | 'Decanting your Statistics',
13 | 'Bottling your Pods',
14 | 'Aerating your Metrics',
15 | 'Finding a Pairing for your Nodes',
16 | 'Starting Grapevine Monitoring',
17 | 'Analyzing your Vintage',
18 | 'Sipping Service Statistics',
19 | 'Cellaring Container Data',
20 | 'You are the Sommelier of Scalability',
21 | ];
22 |
23 | const [message, setMessage] = useState(
24 | messageArray[Math.floor(Math.random() * messageArray.length)]
25 | );
26 |
27 | // Rotate through array of loading messages
28 | useEffect(() => {
29 | const interval = setInterval(() => {
30 | const randomIndex = Math.floor(Math.random() * messageArray.length);
31 | setMessage(messageArray[randomIndex]);
32 | }, 3000);
33 |
34 | return () => clearInterval(interval);
35 | }, []);
36 |
37 | return (
38 |
39 |
44 |
45 | {message}...
46 |
47 |
48 | );
49 | }
50 |
51 | export default WineGlass;
52 |
--------------------------------------------------------------------------------
/client/src/assets/WineSwirl.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Kubernet/0479184ee5e94f6b8100651a843b61edaf2103a8/client/src/assets/WineSwirl.gif
--------------------------------------------------------------------------------
/client/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Kubernet/0479184ee5e94f6b8100651a843b61edaf2103a8/client/src/assets/logo.png
--------------------------------------------------------------------------------
/client/src/components/RootLayout.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink, Outlet } from 'react-router-dom';
3 |
4 | import styles from '../styles/RootLayout.module.scss';
5 |
6 | import logo from '../assets/logo.png';
7 |
8 | function RootLayout() {
9 | return (
10 |
11 |
30 |
31 |
32 |
33 | © 2023 Kubernét
34 |
35 |
36 | );
37 | }
38 |
39 | export default RootLayout;
40 |
--------------------------------------------------------------------------------
/client/src/pages/Dashboard.tsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React, { useEffect } from 'react';
3 | import { useNavigate, useLocation } from 'react-router-dom';
4 |
5 | // Styles
6 | import styles from '../styles/Dashboard.module.scss';
7 |
8 | function Dashboard(): JSX.Element {
9 | const location = useLocation();
10 | const data: { [key: string]: string } = location?.state?.data;
11 | const iframeArray: JSX.Element[] = [];
12 | const navigate = useNavigate();
13 |
14 | // Front end authentication
15 | if (!data) {
16 | useEffect(() => navigate('/'), []);
17 | }
18 |
19 | if (data) {
20 | for (const [graph, url] of Object.entries(data)) {
21 | iframeArray.push(
22 |
27 | );
28 | }
29 | return {iframeArray[0]}
;
30 | }
31 | }
32 | export default Dashboard;
33 |
--------------------------------------------------------------------------------
/client/src/pages/Installation.module.scss:
--------------------------------------------------------------------------------
1 | @import '../styles/colors';
2 |
3 | .container {
4 | height: 90%;
5 | margin: 10px;
6 |
7 | display: flex;
8 | align-items: center;
9 | justify-content: center;
10 | }
11 |
12 | .box {
13 | display: flex;
14 | flex-direction: column;
15 |
16 | align-items: center;
17 | justify-content: space-evenly;
18 |
19 | width: 42rem;
20 | height: 24rem;
21 | border: 1px solid $color4;
22 |
23 | border-radius: 10px;
24 |
25 | background: $color1;
26 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
27 | backdrop-filter: blur(15px);
28 | -webkit-backdrop-filter: blur(15px);
29 | }
30 |
31 | .primary {
32 | width: 23rem;
33 | height: 8rem;
34 |
35 | background-color: $color2;
36 | color: $color1;
37 |
38 | font-size: 3em;
39 |
40 | border-radius: 10px;
41 | border: 1px solid transparent;
42 |
43 | transform: scale(1);
44 | transition-duration: 300ms;
45 | }
46 |
47 | .secondary {
48 | background-color: transparent;
49 | color: $color2;
50 |
51 | border: 1px solid transparent;
52 |
53 | padding: 0;
54 |
55 | font-size: 1.25em;
56 | }
57 |
58 | .primary:hover,
59 | .secondary:hover {
60 | cursor: pointer;
61 | }
62 |
63 | .primary:hover {
64 | transform: scale(1.15);
65 | transition-duration: 300ms;
66 | }
67 |
68 |
69 | .secondary:hover {
70 | text-decoration: underline;
71 | }
72 |
--------------------------------------------------------------------------------
/client/src/pages/Installation.tsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React, { useState } from 'react';
3 | import { useNavigate } from 'react-router-dom';
4 |
5 | // Styles
6 | import styles from './Installation.module.scss';
7 |
8 | function Installation(): JSX.Element {
9 | const navigate = useNavigate();
10 |
11 | const killPort = async () => {
12 | await fetch('http://localhost:5050/killPort')
13 | }
14 |
15 | let render: JSX.Element = (
16 |
17 |
18 |
19 | {
20 | killPort();
21 | navigate('/signupPage');
22 | }}>
23 | Get Started
24 |
25 | {
26 | killPort();
27 | navigate('/loginPage');
28 | }}>
29 | already have an account?
30 |
31 |
32 |
33 |
34 | );
35 |
36 | return {render} ;
37 | }
38 |
39 | export default Installation;
40 |
--------------------------------------------------------------------------------
/client/src/pages/LoginPage.tsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React, { useState } from 'react';
3 | import { useNavigate } from 'react-router-dom';
4 |
5 | // Styles
6 | import styles from '../styles/Home.module.scss';
7 |
8 | // Child Component
9 | import WineGlass from '../assets/WineGlass';
10 |
11 | function LoginPage(): JSX.Element {
12 | const [loading, setLoading] = useState(false);
13 | const navigate = useNavigate();
14 |
15 | // User authentication
16 | const handleLogin = async (e: React.FormEvent ): Promise => {
17 | e.preventDefault();
18 | try {
19 | setLoading(true);
20 | const usernameInput = e.currentTarget.elements[0] as HTMLInputElement;
21 | const passwordInput = e.currentTarget.elements[1] as HTMLInputElement;
22 |
23 | const username: string = usernameInput.value;
24 | const password: string = passwordInput.value;
25 |
26 | const response = await fetch('http://localhost:5050/login', {
27 | method: 'post',
28 | headers: {
29 | 'Content-Type': 'application/json',
30 | },
31 | body: JSON.stringify({ username, password }),
32 | });
33 |
34 | const data = await response.json();
35 | if (response.ok) {
36 | setLoading(false);
37 | navigate('/dashboard', { state: { data } });
38 | }
39 | else {
40 | setLoading(false);
41 | }
42 |
43 | // What if response was not ok
44 | } catch (err) {
45 | // Better error handler
46 | console.log(err);
47 | }
48 | };
49 |
50 | let render: JSX.Element = (
51 |
52 |
53 |
54 |
69 |
70 | {
73 | navigate('/signupPage');
74 | }}>
75 | create an account
76 |
77 |
78 |
79 |
80 | );
81 |
82 | if (loading) render = ;
83 |
84 | return {render} ;
85 | }
86 |
87 | export default LoginPage;
88 |
--------------------------------------------------------------------------------
/client/src/pages/SignupPage.tsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from 'react';
3 | import { useNavigate } from 'react-router-dom';
4 |
5 | // Styles
6 | import styles from '../styles/Home.module.scss';
7 |
8 |
9 | function SignupPage(): JSX.Element {
10 | const navigate = useNavigate();
11 |
12 | const handleSignup = async (e: React.FormEvent) => {
13 | e.preventDefault();
14 |
15 | try {
16 | const usernameInput = e.currentTarget.elements[0] as HTMLInputElement;
17 | const passwordInput = e.currentTarget.elements[1] as HTMLInputElement;
18 |
19 | const username: string = usernameInput.value;
20 | const password: string = passwordInput.value;
21 |
22 | const response = await fetch('http://localhost:5050/signup', {
23 | method: 'post',
24 | headers: {
25 | 'Content-Type': 'application/json',
26 | },
27 | body: JSON.stringify({ username, password }),
28 | });
29 |
30 | const data = await response.json();
31 |
32 | if (data.Success) {
33 | navigate('/loginPage');
34 | }
35 | } catch (err) {
36 | console.log(err);
37 | }
38 | };
39 |
40 | return (
41 |
42 |
43 |
53 |
54 | {
57 | navigate('/loginPage');
58 | }}>
59 | already have an account?
60 |
61 |
62 |
63 | );
64 | }
65 |
66 | export default SignupPage;
67 |
--------------------------------------------------------------------------------
/client/src/redux/slices/userSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | export const userSlice = createSlice({
4 | name: 'user',
5 |
6 | initialState: {
7 | username: null,
8 | kubernetesAPI: null,
9 | grafana: {},
10 | },
11 |
12 | reducers: {},
13 | });
14 |
15 | export const {} = userSlice.actions;
16 |
17 | export default userSlice.reducer;
18 |
--------------------------------------------------------------------------------
/client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import userReducer from './slices/userSlice';
3 |
4 | export const store = configureStore({
5 | reducer: {
6 | user: userReducer,
7 | },
8 | });
9 |
--------------------------------------------------------------------------------
/client/src/styles/Dashboard.module.scss:
--------------------------------------------------------------------------------
1 | @import '_colors';
2 |
3 | .container {
4 | display: flex;
5 | // flex-direction: column;
6 | justify-content: center;
7 | height: 100%;
8 | // border: 1px solid black;
9 | width: 100%;
10 | }
11 |
12 | iframe {
13 | // margin: 10px;
14 | width: 100%;
15 | // border: 30px solid $color1;
16 | // padding: 10px;
17 | border-radius: 5px;
18 | }
19 |
--------------------------------------------------------------------------------
/client/src/styles/Home.module.scss:
--------------------------------------------------------------------------------
1 | @import '_colors.scss';
2 | .box {
3 | display: flex;
4 | flex-direction: column;
5 |
6 | align-items: center;
7 | justify-content: space-evenly;
8 |
9 | width: 42rem;
10 | height: 24rem;
11 | border: 1px solid $color4;
12 |
13 | border-radius: 10px;
14 |
15 | background: $color1;
16 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
17 | backdrop-filter: blur(15px);
18 | -webkit-backdrop-filter: blur(15px);
19 | }
20 |
21 | .form {
22 | display: flex;
23 | flex-direction: column;
24 | align-items: center;
25 | justify-content: space-evenly;
26 |
27 | width: 70%;
28 | height: 60%;
29 | }
30 |
31 | .container {
32 | height: 90%;
33 | margin: 10px;
34 | display: flex;
35 | align-items: center;
36 | justify-content: center;
37 | }
38 |
39 | .input {
40 | display: flex;
41 | flex-direction: column;
42 | align-items: center;
43 | justify-content: center;
44 |
45 | width: 70%;
46 | height: 3rem;
47 |
48 | font-size: 1.25rem;
49 | text-align: center;
50 |
51 | border: 2px solid $color2;
52 | border-radius: 10px;
53 | }
54 |
55 | .input:hover {
56 | background-color: rgb(251, 251, 251);
57 | }
58 |
59 | .input:focus::placeholder {
60 | color: transparent;
61 | }
62 |
63 | .primary {
64 | width: 60%;
65 | height: 3rem;
66 |
67 | color: $color1;
68 | background-color: $color2;
69 |
70 | border: 1px solid transparent;
71 | border-radius: 10px;
72 |
73 | font-size: 1.25rem;
74 |
75 | transform: scale(1);
76 | transition-duration: 300ms;
77 | }
78 |
79 | .primary:hover {
80 | cursor: pointer;
81 |
82 | transform: scale(1.15);
83 | transition-duration: 300ms;
84 | }
85 |
86 | .secondary {
87 | background-color: transparent;
88 | color: $color2;
89 |
90 | font-size: 1.25rem;
91 |
92 | border: 1px solid transparent;
93 | }
94 |
95 | .secondary:hover {
96 | cursor: pointer;
97 | text-decoration: underline;
98 | }
99 |
--------------------------------------------------------------------------------
/client/src/styles/RootLayout.module.scss:
--------------------------------------------------------------------------------
1 | @import '_colors';
2 |
3 | .container {
4 | display: flex;
5 | flex-direction: column;
6 | height: 100vh;
7 | margin: 0px;
8 | }
9 |
10 | .header {
11 | width: 100%;
12 |
13 | display: flex;
14 | flex: 1;
15 | align-items: center;
16 | justify-content: space-between;
17 |
18 | background-color: $color2;
19 | }
20 |
21 | .div {
22 | height: 100%;
23 |
24 | display: flex;
25 | align-items: center;
26 | justify-content: space-around;
27 | width: 33%;
28 | }
29 |
30 | .logo {
31 | height: 3rem;
32 |
33 | position: relative;
34 | left: 5rem;
35 | }
36 |
37 | .link {
38 | margin: 0 10rem;
39 |
40 | text-decoration: none;
41 | color: $color1;
42 |
43 | font-size: 20px;
44 | }
45 |
46 | .title {
47 | font-size: 32px;
48 | font-family: monospace; //Consider a unique font for name
49 |
50 | color: $color1; //Maybe a different color for the title??
51 | }
52 |
53 | .outlet {
54 | display: flex;
55 | flex-direction: column;
56 | flex: 14;
57 |
58 | background-color: $color4;
59 | }
60 |
61 | .copy {
62 | color: rgba(81, 81, 81, 0.462);
63 |
64 | margin-left: 1.25rem;
65 | }
66 |
--------------------------------------------------------------------------------
/client/src/styles/_colors.scss:
--------------------------------------------------------------------------------
1 | $color1: #f8f7f2;
2 | $color2: #22577a;
3 | $color3: #c31723;
4 | $color4: #c6d2dd;
5 | * {
6 | font-family: Arial, Helvetica, sans-serif;
7 | }
8 |
--------------------------------------------------------------------------------
/client/types.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.scss';
2 | declare module '*.gif';
3 |
--------------------------------------------------------------------------------
/mocks/WineSwirl.mock.js:
--------------------------------------------------------------------------------
1 | module.exports = '/Users/praiseemmanuel/codesmith/OSP/Team-Kubernetes/__tests__/__mocks__/WineSwirl.gif'
--------------------------------------------------------------------------------
/mocks/handlers.js:
--------------------------------------------------------------------------------
1 |
2 | import {rest} from 'msw';
3 |
4 | export const handlers = [
5 | rest.get('http://localhost:5050/install', (req, res, ctx) => {
6 | return res(ctx.json([{
7 | username: 'james',
8 | password: 'james123'
9 | }]))
10 | }),
11 |
12 | rest.get('http://localhost:5050/portforward', (req, res, ctx) => {
13 | return res(ctx.json([{
14 | status: 200,
15 | message: 'Port-Forward Successful'
16 | }]))
17 | }),
18 |
19 | rest.get('http://localhost:5050/killPort', (req, res, ctx) => {
20 | return res(ctx.json([{
21 | status: 200
22 | }]))
23 | }),
24 |
25 | rest.get('http://localhost/james', (req, res, ctx) => {
26 | return res(ctx.json([{
27 | status: 200
28 | }]))
29 | }),
30 |
31 | rest.post('http://localhost:5050/signup', (req, res, ctx) => {
32 | return res(
33 | ctx.status(201),
34 | ctx.json({
35 | username: 'james',
36 | password: 'james123',
37 | })
38 | )
39 | }),
40 |
41 | rest.post('http://localhost:5050/login', (req, res, ctx) => {
42 | return res(
43 | ctx.status(201),
44 | ctx.json({
45 | username: 'james',
46 | password: 'james123',
47 | })
48 | )
49 | })
50 | ];
51 |
--------------------------------------------------------------------------------
/mocks/server.js:
--------------------------------------------------------------------------------
1 | import { handlers } from "./handlers";
2 | import {setupServer} from 'msw/node';
3 | export const server = setupServer(...handlers);
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "osp1",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "jest": {
7 | "moduleNameMapper": {
8 | "\\.(css|scss|gif|png|jpg|jpeg|svg)$": "identity-obj-proxy"
9 | },
10 | "testEnvironment": "jsdom",
11 | "testEnvironmentOptions": {
12 | "resources": "usable"
13 | },
14 | "setupFilesAfterEnv": [
15 | "./test-setup.js"
16 | ]
17 | },
18 | "scripts": {
19 | "test": "jest test --verbose",
20 | "frontend": "NODE_ENV=development webpack-dev-server --open --hot --progress --color",
21 | "start": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --open --hot --progress --color \" \"NODE_ENV=development nodemon ./server/server.ts\"",
22 | "build": "NODE_ENV=production webpack",
23 | "server": "NODE_ENV=production nodemon ./server/server.js"
24 | },
25 | "keywords": [],
26 | "author": "Joseph, Paul, Praise, Jimmy",
27 | "license": "ISC",
28 | "dependencies": {
29 | "@reduxjs/toolkit": "^1.9.5",
30 | "@types/react": "^18.2.11",
31 | "@types/react-dom": "^18.2.4",
32 | "bcryptjs": "^2.4.3",
33 | "cookie-parser": "^1.4.6",
34 | "cors": "^2.8.5",
35 | "dotenv": "^16.1.4",
36 | "express": "^4.18.2",
37 | "mongoose": "^7.2.2",
38 | "react": "^18.2.0",
39 | "react-dom": "^18.2.0",
40 | "react-redux": "^8.0.5",
41 | "react-router": "^6.11.2",
42 | "react-router-dom": "^6.11.2",
43 | "redux": "^4.2.1"
44 | },
45 | "devDependencies": {
46 | "@babel/core": "^7.22.1",
47 | "@babel/plugin-syntax-jsx": "^7.22.5",
48 | "@babel/preset-env": "^7.22.4",
49 | "@babel/preset-react": "^7.22.3",
50 | "@babel/preset-typescript": "^7.22.5",
51 | "@testing-library/jest-dom": "^5.16.5",
52 | "@testing-library/react": "^14.0.0",
53 | "@testing-library/user-event": "^14.4.3",
54 | "@types/bcryptjs": "^2.4.2",
55 | "@types/cookie-parser": "^1.4.3",
56 | "@types/cors": "^2.8.13",
57 | "@types/express": "^4.17.17",
58 | "@types/jest": "^29.5.2",
59 | "@types/node": "^20.3.1",
60 | "babel-jest": "^29.5.0",
61 | "@types/supertest": "^2.0.12",
62 | "babel-loader": "^9.1.2",
63 | "concurrently": "^8.0.1",
64 | "cross-env": "^7.0.3",
65 | "css-loader": "^6.8.1",
66 | "file-loader": "^6.2.0",
67 | "html-webpack-plugin": "^5.5.1",
68 | "identity-obj-proxy": "^3.0.0",
69 | "jest": "^29.5.0",
70 | "jest-environment-jsdom": "^29.5.0",
71 | "mini-css-extract-plugin": "^2.7.6",
72 | "msw": "^1.2.2",
73 | "nodemon": "^2.0.22",
74 | "redux-devtools-extension": "^2.13.9",
75 | "sass": "^1.62.1",
76 | "sass-loader": "^13.3.1",
77 | "style-loader": "^3.3.3",
78 | "supertest": "^6.3.3",
79 | "ts-jest": "^29.1.0",
80 | "ts-loader": "^9.4.3",
81 | "ts-node": "^10.9.1",
82 | "typescript": "^5.1.3",
83 | "typings-for-css-modules-loader": "^1.7.0",
84 | "webpack": "^5.84.1",
85 | "webpack-cli": "^5.1.1",
86 | "webpack-dev-server": "^4.15.0",
87 | "whatwg-fetch": "^3.6.2"
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/prometheus-grafana.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | data:
3 | grafana.ini: "[analytics]\ncheck_for_updates = true\n[grafana_net]\nurl = https://grafana.net\n[log]\nmode
4 | = console\n[paths]\ndata = /var/lib/grafana/\nlogs = /var/log/grafana\nplugins
5 | = /var/lib/grafana/plugins\nprovisioning = /etc/grafana/provisioning\n[server]\ndomain
6 | = ''\n[security] \nallow_embedding = true\n[auth.anonymous] \nenabled = true \norg_role
7 | = Admin"
8 | kind: ConfigMap
9 | metadata:
10 | annotations:
11 | meta.helm.sh/release-name: prometheus
12 | meta.helm.sh/release-namespace: default
13 | creationTimestamp: "2023-05-25T23:43:17Z"
14 | labels:
15 | app.kubernetes.io/instance: prometheus
16 | app.kubernetes.io/managed-by: Helm
17 | app.kubernetes.io/name: grafana
18 | app.kubernetes.io/version: 9.5.2
19 | helm.sh/chart: grafana-6.56.5
20 | name: prometheus-grafana
21 | namespace: default
22 | resourceVersion: "54656"
23 | uid: 9b6c25d9-f1fc-4f3f-90ca-5a91eea243fa
24 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/public/readme/K-Github-Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Kubernet/0479184ee5e94f6b8100651a843b61edaf2103a8/public/readme/K-Github-Logo.png
--------------------------------------------------------------------------------
/public/readme/dashboard.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Kubernet/0479184ee5e94f6b8100651a843b61edaf2103a8/public/readme/dashboard.gif
--------------------------------------------------------------------------------
/public/readme/getstarted.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Kubernet/0479184ee5e94f6b8100651a843b61edaf2103a8/public/readme/getstarted.gif
--------------------------------------------------------------------------------
/public/readme/github_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Kubernet/0479184ee5e94f6b8100651a843b61edaf2103a8/public/readme/github_icon.png
--------------------------------------------------------------------------------
/public/readme/linkedin_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Kubernet/0479184ee5e94f6b8100651a843b61edaf2103a8/public/readme/linkedin_icon.png
--------------------------------------------------------------------------------
/public/readme/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Kubernet/0479184ee5e94f6b8100651a843b61edaf2103a8/public/readme/loading.gif
--------------------------------------------------------------------------------
/public/readme/tests.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Kubernet/0479184ee5e94f6b8100651a843b61edaf2103a8/public/readme/tests.png
--------------------------------------------------------------------------------
/server/controllers/cookieController.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction } from 'express';
2 |
3 | const cookieController = {
4 | setSSIDCookie: (req: Request, res: Response, next: NextFunction) => {
5 | res.cookie('ssid', res.locals.user.id, { httpOnly: true });
6 | return next();
7 | },
8 | };
9 |
10 | export default cookieController;
11 |
--------------------------------------------------------------------------------
/server/controllers/grafanaController.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction } from 'express';
2 | import fullDashboard from '../panels/KubernetFullDash.json';
3 |
4 | const urlStore: { fullDashboard?: string } = {};
5 |
6 | const grafanaController = {
7 | createDashboard: (req: Request, res: Response, next: NextFunction) => {
8 | res.locals.createdNewDashboard = false;
9 | if (res.locals.URLS) return next();
10 | fetch('http://localhost:3000/api/dashboards/db', {
11 | method: 'POST',
12 | headers: {
13 | Accept: 'application/json',
14 | 'Content-Type': 'application/json',
15 | },
16 | body: JSON.stringify({
17 | dashboard: {
18 | id: null,
19 | uid: null,
20 | title: `Kubernét`,
21 | timezone: 'browser',
22 | tags: ['templated'],
23 | schemaVersion: 16,
24 | version: 0,
25 | refresh: '15s',
26 | panels: fullDashboard,
27 | },
28 | folderId: 0,
29 | message: '',
30 | overwrite: false,
31 | }),
32 | })
33 | .then((data) => data.json())
34 | .then((data) => {
35 | const { uid } = data;
36 | urlStore.fullDashboard = `http://localhost:3000/d/${uid}/KubernetSuperSpecialDashboard?theme=light&orgId=1&refresh=5s`;
37 | res.locals.URLS = urlStore;
38 | res.locals.createdNewDashboard = true;
39 | return next();
40 | })
41 | .catch((err) => {
42 | console.log(err);
43 | next(err);
44 | });
45 | },
46 | };
47 |
48 | export default grafanaController;
49 |
--------------------------------------------------------------------------------
/server/controllers/installController.ts:
--------------------------------------------------------------------------------
1 | import { spawn, spawnSync } from 'child_process';
2 | import { Request, Response, NextFunction } from 'express';
3 |
4 | interface InstallController {
5 | promInstall: (req: Request, res: Response, next: NextFunction) => void;
6 | grafEmbed: (req: Request, res: Response, next: NextFunction) => void;
7 | portForward: (req: Request, res: Response, next: NextFunction) => void;
8 | killPort: (req: Request, res: Response, next: NextFunction) => void;
9 | }
10 |
11 | const installController: InstallController = {
12 | /**
13 | * Install Prometheus
14 | */
15 | promInstall: (req: Request, res: Response, next: NextFunction) => {
16 | if (res.locals.URLS) return next();
17 | console.log('Initializing prometheus!');
18 |
19 | // Adding prometheus repo to helm
20 | spawnSync(
21 | 'helm repo add prometheus-community https://prometheus-community.github.io/helm-charts',
22 | { stdio: 'inherit', shell: true }
23 | );
24 |
25 | // Updating the repo
26 | spawnSync('helm repo update', { stdio: 'inherit', shell: true });
27 |
28 | // Installs helm chart with the label prometheusOSP
29 | spawnSync(
30 | 'helm install prometheus prometheus-community/kube-prometheus-stack',
31 | { stdio: 'inherit', shell: true }
32 | );
33 |
34 | return next();
35 | },
36 | /**
37 | * Embed Grafana
38 | */
39 | grafEmbed: (req: Request, res: Response, next: NextFunction) => {
40 | if (res.locals.URLS) return next();
41 | console.log('Embedding grafana');
42 |
43 | // Executing kubectl get pods
44 | const child = spawnSync('kubectl', ['get', 'pods'], { encoding: 'utf-8' });
45 |
46 | // Error handler
47 | if (child.stderr) {
48 | console.error(`kubectl get pods error: ${child.stderr}`);
49 | return next({
50 | log: 'Express error handler caught! grafEmbed middleware error',
51 | status: 500,
52 | message: {
53 | err: 'An error occurred while attempting to get pods',
54 | },
55 | });
56 | }
57 | // Assigning the console statement to a constant
58 | const output = child.stdout.split('\n');
59 |
60 | // Searching for the prometheus-grafana pod
61 | let pod;
62 | output.forEach((line) => {
63 | if (line.includes('prometheus-grafana')) {
64 | [pod] = line.split(' ');
65 | }
66 | });
67 |
68 | // Delete previous config map
69 | spawnSync('kubectl delete configmap prometheus-grafana', {
70 | stdio: 'inherit',
71 | shell: true,
72 | });
73 |
74 | // Applying custom yaml file
75 | spawnSync('kubectl apply -f prometheus-grafana.yaml', {
76 | stdio: 'inherit',
77 | shell: true,
78 | });
79 |
80 | // Delete old prometheus-grafana pod
81 | spawnSync(`kubectl delete pod ${pod}`, { stdio: 'inherit', shell: true });
82 |
83 | setTimeout(() => next(), 6000);
84 | },
85 | /**
86 | * Forward Ports
87 | */
88 | portForward: (req: Request, res: Response, next: NextFunction) => {
89 | console.log('Forwarding ports to localhost:3000');
90 |
91 | // Attempts for forward cluster port 3000 to localhost 3000
92 | const port = spawn(
93 | 'kubectl port-forward deployment/prometheus-grafana 3000',
94 | { shell: true }
95 | );
96 |
97 | // Moves to next middleware if port forward was successful
98 | port.stdout.on('data', (data) => {
99 | console.log('Success! Grafana can now be accessed on localhost:3000');
100 | });
101 |
102 | port.stderr.on('data', (data) => {
103 | console.error(`grafana port forwarding error: ${data}`);
104 | return next({
105 | log: 'Express error handler caught! portForward middleware error',
106 | status: 500,
107 | message: {
108 | err: 'An error occurred while attempting to forward a port to localhost:3000',
109 | },
110 | });
111 | });
112 |
113 | setTimeout(() => {
114 | return next()}, 6000);
115 | },
116 |
117 | killPort: (req: Request, res: Response, next: NextFunction) => {
118 | spawn('pkill -f "port-forward"', { stdio: 'inherit', shell: true });
119 | return next();
120 | }
121 | };
122 |
123 | export default installController;
124 |
--------------------------------------------------------------------------------
/server/controllers/sessionController.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction } from 'express';
2 | import Session from '../models/sessionModel';
3 |
4 | const sessionController = {
5 | startSession: async (req: Request, res: Response, next: NextFunction) => {
6 | try {
7 | await Session.findOneAndUpdate(
8 | { cookieId: res.locals.user.id },
9 | { createdAt: Date.now() },
10 | { upsert: true, setDefaultsOnInsert: true }
11 | );
12 | return next();
13 | } catch (err: any) {
14 | return next({
15 | log: 'error in startSession controller',
16 | status: 500,
17 | message: 'Error in creating/finding cookie',
18 | });
19 | }
20 | },
21 | };
22 |
23 | export default sessionController;
24 |
--------------------------------------------------------------------------------
/server/controllers/userController.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction } from 'express';
2 | import User from '../models/UserModel';
3 | import bcrypt from 'bcryptjs';
4 |
5 | interface UserController {
6 | createUser: (req: Request, res: Response, next: NextFunction) => void;
7 | verifyUser: (req: Request, res: Response, next: NextFunction) => void;
8 | deleteUser: (req: Request, res: Response, next: NextFunction) => void;
9 | addUrls: (req: Request, res: Response, next: NextFunction) => void;
10 | // getUrls: (req: Request, res: Response, next: NextFunction) => void;
11 | }
12 |
13 | const userController: UserController = {
14 | createUser: (req: Request, res: Response, next: NextFunction) => {
15 | const { username, password } = req.body;
16 | User.create({ username, password })
17 | .then((newUser) => {
18 | res.locals.user = newUser;
19 | return next();
20 | })
21 | .catch((err: any) => {
22 | return next({
23 | log: `createUser: ${err}`,
24 | status: 400,
25 | message: { err: 'An error occured in the createUser controller' },
26 | });
27 | });
28 | },
29 |
30 | verifyUser: (req: Request, res: Response, next: NextFunction) => {
31 | const { username, password } = req.body;
32 | User.findOne({ username })
33 | .then((user) => {
34 | if (!user) {
35 | return next({ log: `error`,
36 | status: 401,
37 | message: { err: `error occured in verifUser middleware function` },
38 | })
39 | } else {
40 | bcrypt.compare(password, user.password).then((result) => {
41 | if (!result) {
42 | return next({ log: `error`,
43 | status: 401,
44 | message: { err: `error occured in verifUser middleware function` },
45 | })
46 | } else {
47 | res.locals.user = user;
48 | if (user.urls) res.locals.URLS = user.urls[0];
49 | return next();
50 | }
51 | });
52 | }
53 | })
54 | .catch((err: any) => {
55 | return next({
56 | log: `error ${err}`,
57 | status: 400,
58 | message: { err: `error occured in login controller` },
59 | });
60 | });
61 | },
62 | deleteUser: (req: Request, res: Response, next: NextFunction) => {
63 | const id = res.locals.user;
64 | User.findOneAndDelete({ _id: id })
65 | .then(user => next())
66 | .catch(err => next({
67 | log: `error ${err}`,
68 | status: 500,
69 | message: { err: `error occured in deleteUser middleware function` },
70 | }));
71 |
72 | },
73 | addUrls: (req: Request, res: Response, next: NextFunction) => {
74 | if (res.locals.createdNewDashboard === false) {
75 | return next()
76 | };
77 | const id = res.locals.user.id;
78 | User.findOneAndUpdate({ _id: id },{ $push: { urls: res.locals.URLS } })
79 | .then(user => {
80 | return next()})
81 | .catch(err => next({
82 | log: `error ${err}`,
83 | status: 500,
84 | message: { err: `error occured in deleteUser middleware function` },
85 | }))
86 | },
87 | };
88 |
89 | export default userController;
90 |
--------------------------------------------------------------------------------
/server/models/UserModel.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 | import bcrypt from 'bcryptjs';
3 |
4 | const userSchema = new mongoose.Schema({
5 | username: { type: String, required: true, unique: true },
6 | password: { type: String, required: true },
7 | urls: { type: Object },
8 | });
9 |
10 | //Bcrypt Functionality:
11 | const SALT_WORK_FACTOR: number = 10;
12 |
13 | userSchema.pre('save', function (next) {
14 | bcrypt.hash(this.password, SALT_WORK_FACTOR, (err, hash) => {
15 | if (err) return next(err);
16 |
17 | this.password = hash;
18 | return next();
19 | });
20 | });
21 |
22 | const User = mongoose.model('User', userSchema);
23 |
24 | export default User;
25 |
--------------------------------------------------------------------------------
/server/models/sessionModel.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 |
3 | const sessionSchema = new mongoose.Schema({
4 | cookieId: { type: String, required: true, unique: true },
5 | createdAt: { type: Date, expires: 30, default: Date.now },
6 | });
7 |
8 | const Session = mongoose.model('session', sessionSchema);
9 |
10 | export default Session;
11 |
--------------------------------------------------------------------------------
/server/panels/KubernetFullDash.json:
--------------------------------------------------------------------------------
1 |
2 | [{
3 | "datasource": {
4 | "type": "prometheus",
5 | "uid": "prometheus"
6 | },
7 | "fieldConfig": {
8 | "defaults": {
9 | "color": {
10 | "fixedColor": "#22577a",
11 | "mode": "fixed"
12 | },
13 | "mappings": [],
14 | "thresholds": {
15 | "mode": "absolute",
16 | "steps": [
17 | {
18 | "color": "green",
19 | "value": null
20 | },
21 | {
22 | "color": "red",
23 | "value": 80
24 | }
25 | ]
26 | },
27 | "unit": "percentunit"
28 | },
29 | "overrides": []
30 | },
31 | "gridPos": {
32 | "h": 8,
33 | "w": 8,
34 | "x": 0,
35 | "y": 0
36 | },
37 | "id": 156,
38 | "options": {
39 | "colorMode": "background_solid",
40 | "graphMode": "none",
41 | "justifyMode": "center",
42 | "orientation": "auto",
43 | "reduceOptions": {
44 | "calcs": [
45 | "lastNotNull"
46 | ],
47 | "fields": "",
48 | "values": false
49 | },
50 | "textMode": "value"
51 | },
52 | "pluginVersion": "9.5.2",
53 | "targets": [
54 | {
55 | "datasource": {
56 | "type": "prometheus",
57 | "uid": "prometheus"
58 | },
59 | "editorMode": "code",
60 | "expr": "cluster:node_cpu:ratio_rate5m",
61 | "legendFormat": "__auto",
62 | "range": true,
63 | "refId": "A"
64 | }
65 | ],
66 | "title": "Cluster CPU Utilisation",
67 | "transparent": true,
68 | "type": "stat"
69 | },
70 | {
71 | "datasource": {
72 | "type": "prometheus",
73 | "uid": "prometheus"
74 | },
75 | "fieldConfig": {
76 | "defaults": {
77 | "color": {
78 | "fixedColor": "#22577a",
79 | "mode": "fixed"
80 | },
81 | "mappings": [],
82 | "thresholds": {
83 | "mode": "absolute",
84 | "steps": [
85 | {
86 | "color": "green",
87 | "value": null
88 | },
89 | {
90 | "color": "red",
91 | "value": 80
92 | }
93 | ]
94 | },
95 | "unit": "percentunit"
96 | },
97 | "overrides": []
98 | },
99 | "gridPos": {
100 | "h": 8,
101 | "w": 8,
102 | "x": 8,
103 | "y": 0
104 | },
105 | "id": 159,
106 | "options": {
107 | "colorMode": "background_solid",
108 | "graphMode": "none",
109 | "justifyMode": "center",
110 | "orientation": "auto",
111 | "reduceOptions": {
112 | "calcs": [
113 | "lastNotNull"
114 | ],
115 | "fields": "",
116 | "values": false
117 | },
118 | "textMode": "value"
119 | },
120 | "pluginVersion": "9.5.2",
121 | "targets": [
122 | {
123 | "datasource": {
124 | "type": "prometheus",
125 | "uid": "prometheus"
126 | },
127 | "editorMode": "code",
128 | "expr": "sum(namespace_cpu:kube_pod_container_resource_requests:sum{cluster=\"\"}) / sum(kube_node_status_allocatable{job=\"kube-state-metrics\",resource=\"cpu\",cluster=\"\"})",
129 | "legendFormat": "__auto",
130 | "range": true,
131 | "refId": "A"
132 | }
133 | ],
134 | "title": "Cluster CPU Requests Commitment",
135 | "transparent": true,
136 | "type": "stat"
137 | },
138 | {
139 | "datasource": {
140 | "type": "prometheus",
141 | "uid": "prometheus"
142 | },
143 | "fieldConfig": {
144 | "defaults": {
145 | "color": {
146 | "fixedColor": "#22577a",
147 | "mode": "fixed"
148 | },
149 | "mappings": [],
150 | "thresholds": {
151 | "mode": "absolute",
152 | "steps": [
153 | {
154 | "color": "green",
155 | "value": null
156 | },
157 | {
158 | "color": "red",
159 | "value": 80
160 | }
161 | ]
162 | },
163 | "unit": "percentunit"
164 | },
165 | "overrides": []
166 | },
167 | "gridPos": {
168 | "h": 8,
169 | "w": 8,
170 | "x": 16,
171 | "y": 0
172 | },
173 | "id": 161,
174 | "options": {
175 | "colorMode": "background_solid",
176 | "graphMode": "none",
177 | "justifyMode": "center",
178 | "orientation": "auto",
179 | "reduceOptions": {
180 | "calcs": [
181 | "lastNotNull"
182 | ],
183 | "fields": "",
184 | "values": false
185 | },
186 | "textMode": "value"
187 | },
188 | "pluginVersion": "9.5.2",
189 | "targets": [
190 | {
191 | "datasource": {
192 | "type": "prometheus",
193 | "uid": "prometheus"
194 | },
195 | "editorMode": "code",
196 | "expr": "sum(namespace_cpu:kube_pod_container_resource_limits:sum{cluster=\"\"}) / sum(kube_node_status_allocatable{job=\"kube-state-metrics\",resource=\"cpu\",cluster=\"\"})",
197 | "legendFormat": "__auto",
198 | "range": true,
199 | "refId": "A"
200 | }
201 | ],
202 | "title": "Cluster CPU Limits Commitment",
203 | "transparent": true,
204 | "type": "stat"
205 | },
206 | {
207 | "datasource": {
208 | "type": "prometheus",
209 | "uid": "prometheus"
210 | },
211 | "fieldConfig": {
212 | "defaults": {
213 | "color": {
214 | "fixedColor": "#22577a",
215 | "mode": "fixed",
216 | "seriesBy": "last"
217 | },
218 | "custom": {
219 | "axisCenteredZero": false,
220 | "axisColorMode": "text",
221 | "axisLabel": "",
222 | "axisPlacement": "auto",
223 | "barAlignment": 0,
224 | "drawStyle": "line",
225 | "fillOpacity": 100,
226 | "gradientMode": "hue",
227 | "hideFrom": {
228 | "legend": false,
229 | "tooltip": false,
230 | "viz": false
231 | },
232 | "lineInterpolation": "stepAfter",
233 | "lineStyle": {
234 | "fill": "solid"
235 | },
236 | "lineWidth": 0,
237 | "pointSize": 1,
238 | "scaleDistribution": {
239 | "type": "linear"
240 | },
241 | "showPoints": "never",
242 | "spanNulls": false,
243 | "stacking": {
244 | "group": "A",
245 | "mode": "none"
246 | },
247 | "thresholdsStyle": {
248 | "mode": "off"
249 | }
250 | },
251 | "decimals": 1,
252 | "mappings": [],
253 | "thresholds": {
254 | "mode": "absolute",
255 | "steps": [
256 | {
257 | "color": "green",
258 | "value": null
259 | },
260 | {
261 | "color": "#EAB839",
262 | "value": 80
263 | }
264 | ]
265 | },
266 | "unit": "percentunit"
267 | },
268 | "overrides": []
269 | },
270 | "gridPos": {
271 | "h": 8,
272 | "w": 12,
273 | "x": 0,
274 | "y": 8
275 | },
276 | "id": 155,
277 | "options": {
278 | "legend": {
279 | "calcs": [],
280 | "displayMode": "list",
281 | "placement": "bottom",
282 | "showLegend": false
283 | },
284 | "tooltip": {
285 | "mode": "single",
286 | "sort": "none"
287 | }
288 | },
289 | "pluginVersion": "9.5.2",
290 | "targets": [
291 | {
292 | "datasource": {
293 | "type": "prometheus",
294 | "uid": "prometheus"
295 | },
296 | "editorMode": "code",
297 | "exemplar": true,
298 | "expr": "cluster:node_cpu:ratio_rate5m",
299 | "interval": "1",
300 | "legendFormat": "__auto",
301 | "range": true,
302 | "refId": "A"
303 | }
304 | ],
305 | "title": "Cluster CPU Utilisation",
306 | "transparent": true,
307 | "type": "timeseries"
308 | },
309 | {
310 | "datasource": {
311 | "type": "prometheus",
312 | "uid": "prometheus"
313 | },
314 | "fieldConfig": {
315 | "defaults": {
316 | "color": {
317 | "fixedColor": "#22577a",
318 | "mode": "palette-classic",
319 | "seriesBy": "last"
320 | },
321 | "custom": {
322 | "axisCenteredZero": false,
323 | "axisColorMode": "text",
324 | "axisLabel": "",
325 | "axisPlacement": "auto",
326 | "barAlignment": 0,
327 | "drawStyle": "line",
328 | "fillOpacity": 0,
329 | "gradientMode": "none",
330 | "hideFrom": {
331 | "legend": false,
332 | "tooltip": false,
333 | "viz": false
334 | },
335 | "lineInterpolation": "linear",
336 | "lineStyle": {
337 | "fill": "solid"
338 | },
339 | "lineWidth": 1,
340 | "pointSize": 5,
341 | "scaleDistribution": {
342 | "type": "linear"
343 | },
344 | "showPoints": "never",
345 | "spanNulls": false,
346 | "stacking": {
347 | "group": "A",
348 | "mode": "none"
349 | },
350 | "thresholdsStyle": {
351 | "mode": "off"
352 | }
353 | },
354 | "mappings": [],
355 | "min": 0,
356 | "thresholds": {
357 | "mode": "absolute",
358 | "steps": [
359 | {
360 | "color": "#22577a",
361 | "value": null
362 | }
363 | ]
364 | },
365 | "unit": "short"
366 | },
367 | "overrides": []
368 | },
369 | "gridPos": {
370 | "h": 8,
371 | "w": 12,
372 | "x": 12,
373 | "y": 8
374 | },
375 | "id": 163,
376 | "links": [],
377 | "options": {
378 | "legend": {
379 | "calcs": [],
380 | "displayMode": "list",
381 | "placement": "bottom",
382 | "showLegend": true
383 | },
384 | "tooltip": {
385 | "mode": "multi",
386 | "sort": "none"
387 | }
388 | },
389 | "pluginVersion": "9.5.2",
390 | "targets": [
391 | {
392 | "datasource": {
393 | "uid": "$datasource"
394 | },
395 | "editorMode": "code",
396 | "expr": "node_load1{job=\"node-exporter\"}",
397 | "format": "time_series",
398 | "intervalFactor": 2,
399 | "legendFormat": "1m load average",
400 | "range": true,
401 | "refId": "A"
402 | },
403 | {
404 | "datasource": {
405 | "uid": "$datasource"
406 | },
407 | "editorMode": "code",
408 | "expr": "node_load5{job=\"node-exporter\"}",
409 | "format": "time_series",
410 | "intervalFactor": 2,
411 | "legendFormat": "5m load average",
412 | "range": true,
413 | "refId": "B"
414 | },
415 | {
416 | "datasource": {
417 | "uid": "$datasource"
418 | },
419 | "editorMode": "code",
420 | "expr": "node_load15{job=\"node-exporter\"}",
421 | "format": "time_series",
422 | "intervalFactor": 2,
423 | "legendFormat": "15m load average",
424 | "range": true,
425 | "refId": "C"
426 | }
427 | ],
428 | "title": "Cluster CPU Load Average",
429 | "transparent": true,
430 | "type": "timeseries"
431 | },
432 | {
433 | "datasource": {
434 | "type": "prometheus",
435 | "uid": "prometheus"
436 | },
437 | "fieldConfig": {
438 | "defaults": {
439 | "color": {
440 | "fixedColor": "#22577a",
441 | "mode": "fixed"
442 | },
443 | "mappings": [],
444 | "thresholds": {
445 | "mode": "absolute",
446 | "steps": [
447 | {
448 | "color": "green",
449 | "value": null
450 | },
451 | {
452 | "color": "red",
453 | "value": 80
454 | }
455 | ]
456 | },
457 | "unit": "percentunit"
458 | },
459 | "overrides": []
460 | },
461 | "gridPos": {
462 | "h": 8,
463 | "w": 8,
464 | "x": 0,
465 | "y": 16
466 | },
467 | "id": 158,
468 | "options": {
469 | "colorMode": "background_solid",
470 | "graphMode": "none",
471 | "justifyMode": "auto",
472 | "orientation": "auto",
473 | "reduceOptions": {
474 | "calcs": [
475 | "lastNotNull"
476 | ],
477 | "fields": "",
478 | "values": false
479 | },
480 | "text": {},
481 | "textMode": "auto"
482 | },
483 | "pluginVersion": "9.5.2",
484 | "targets": [
485 | {
486 | "datasource": {
487 | "type": "prometheus",
488 | "uid": "prometheus"
489 | },
490 | "editorMode": "code",
491 | "expr": "1 - sum(:node_memory_MemAvailable_bytes:sum) / sum(node_memory_MemTotal_bytes)",
492 | "legendFormat": "__auto",
493 | "range": true,
494 | "refId": "A"
495 | }
496 | ],
497 | "title": "Cluster Memory Utilisation",
498 | "transparent": true,
499 | "type": "stat"
500 | },
501 | {
502 | "datasource": {
503 | "type": "prometheus",
504 | "uid": "prometheus"
505 | },
506 | "fieldConfig": {
507 | "defaults": {
508 | "color": {
509 | "fixedColor": "#22577a",
510 | "mode": "fixed"
511 | },
512 | "mappings": [],
513 | "thresholds": {
514 | "mode": "absolute",
515 | "steps": [
516 | {
517 | "color": "green",
518 | "value": null
519 | },
520 | {
521 | "color": "red",
522 | "value": 80
523 | }
524 | ]
525 | },
526 | "unit": "percentunit"
527 | },
528 | "overrides": []
529 | },
530 | "gridPos": {
531 | "h": 8,
532 | "w": 8,
533 | "x": 8,
534 | "y": 16
535 | },
536 | "id": 160,
537 | "options": {
538 | "colorMode": "background_solid",
539 | "graphMode": "none",
540 | "justifyMode": "center",
541 | "orientation": "auto",
542 | "reduceOptions": {
543 | "calcs": [
544 | "lastNotNull"
545 | ],
546 | "fields": "",
547 | "values": false
548 | },
549 | "textMode": "value"
550 | },
551 | "pluginVersion": "9.5.2",
552 | "targets": [
553 | {
554 | "datasource": {
555 | "type": "prometheus",
556 | "uid": "prometheus"
557 | },
558 | "editorMode": "code",
559 | "expr": "sum(namespace_memory:kube_pod_container_resource_requests:sum{cluster=\"\"}) / sum(kube_node_status_allocatable{job=\"kube-state-metrics\",resource=\"memory\",cluster=\"\"})",
560 | "legendFormat": "__auto",
561 | "range": true,
562 | "refId": "A"
563 | }
564 | ],
565 | "title": "Cluster Memory Requests Commitment",
566 | "transparent": true,
567 | "type": "stat"
568 | },
569 | {
570 | "datasource": {
571 | "type": "prometheus",
572 | "uid": "prometheus"
573 | },
574 | "fieldConfig": {
575 | "defaults": {
576 | "color": {
577 | "fixedColor": "#22577a",
578 | "mode": "fixed"
579 | },
580 | "mappings": [],
581 | "thresholds": {
582 | "mode": "absolute",
583 | "steps": [
584 | {
585 | "color": "green",
586 | "value": null
587 | },
588 | {
589 | "color": "red",
590 | "value": 80
591 | }
592 | ]
593 | },
594 | "unit": "percentunit"
595 | },
596 | "overrides": []
597 | },
598 | "gridPos": {
599 | "h": 8,
600 | "w": 8,
601 | "x": 16,
602 | "y": 16
603 | },
604 | "id": 162,
605 | "options": {
606 | "colorMode": "background_solid",
607 | "graphMode": "none",
608 | "justifyMode": "center",
609 | "orientation": "auto",
610 | "reduceOptions": {
611 | "calcs": [
612 | "lastNotNull"
613 | ],
614 | "fields": "",
615 | "values": false
616 | },
617 | "textMode": "value"
618 | },
619 | "pluginVersion": "9.5.2",
620 | "targets": [
621 | {
622 | "datasource": {
623 | "type": "prometheus",
624 | "uid": "prometheus"
625 | },
626 | "editorMode": "code",
627 | "expr": "sum(namespace_memory:kube_pod_container_resource_limits:sum{cluster=\"\"}) / sum(kube_node_status_allocatable{job=\"kube-state-metrics\",resource=\"memory\",cluster=\"\"})",
628 | "legendFormat": "__auto",
629 | "range": true,
630 | "refId": "A"
631 | }
632 | ],
633 | "title": "Cluster Memory Limits Commitment",
634 | "transparent": true,
635 | "type": "stat"
636 | },
637 | {
638 | "datasource": {
639 | "type": "prometheus",
640 | "uid": "prometheus"
641 | },
642 | "fieldConfig": {
643 | "defaults": {
644 | "color": {
645 | "fixedColor": "#22577a",
646 | "mode": "fixed"
647 | },
648 | "custom": {
649 | "axisCenteredZero": false,
650 | "axisColorMode": "text",
651 | "axisLabel": "",
652 | "axisPlacement": "auto",
653 | "barAlignment": 0,
654 | "drawStyle": "line",
655 | "fillOpacity": 100,
656 | "gradientMode": "hue",
657 | "hideFrom": {
658 | "legend": false,
659 | "tooltip": false,
660 | "viz": false
661 | },
662 | "lineInterpolation": "linear",
663 | "lineWidth": 0,
664 | "pointSize": 5,
665 | "scaleDistribution": {
666 | "type": "linear"
667 | },
668 | "showPoints": "never",
669 | "spanNulls": false,
670 | "stacking": {
671 | "group": "A",
672 | "mode": "none"
673 | },
674 | "thresholdsStyle": {
675 | "mode": "off"
676 | }
677 | },
678 | "decimals": 1,
679 | "mappings": [],
680 | "thresholds": {
681 | "mode": "absolute",
682 | "steps": [
683 | {
684 | "color": "green",
685 | "value": null
686 | },
687 | {
688 | "color": "red",
689 | "value": 80
690 | }
691 | ]
692 | },
693 | "unit": "percentunit"
694 | },
695 | "overrides": []
696 | },
697 | "gridPos": {
698 | "h": 9,
699 | "w": 12,
700 | "x": 0,
701 | "y": 24
702 | },
703 | "id": 157,
704 | "options": {
705 | "legend": {
706 | "calcs": [],
707 | "displayMode": "list",
708 | "placement": "bottom",
709 | "showLegend": false
710 | },
711 | "tooltip": {
712 | "mode": "single",
713 | "sort": "none"
714 | }
715 | },
716 | "targets": [
717 | {
718 | "datasource": {
719 | "type": "prometheus",
720 | "uid": "prometheus"
721 | },
722 | "editorMode": "code",
723 | "expr": "1 - sum(:node_memory_MemAvailable_bytes:sum) / sum(node_memory_MemTotal_bytes)",
724 | "legendFormat": "__auto",
725 | "range": true,
726 | "refId": "A"
727 | }
728 | ],
729 | "title": "Cluster Memory Utilisation",
730 | "transparent": true,
731 | "type": "timeseries"
732 | },
733 | {
734 | "datasource": {
735 | "type": "prometheus",
736 | "uid": "prometheus"
737 | },
738 | "fieldConfig": {
739 | "defaults": {
740 | "color": {
741 | "mode": "palette-classic"
742 | },
743 | "custom": {
744 | "axisCenteredZero": false,
745 | "axisColorMode": "text",
746 | "axisLabel": "",
747 | "axisPlacement": "auto",
748 | "barAlignment": 0,
749 | "drawStyle": "line",
750 | "fillOpacity": 10,
751 | "gradientMode": "none",
752 | "hideFrom": {
753 | "legend": false,
754 | "tooltip": false,
755 | "viz": false
756 | },
757 | "lineInterpolation": "linear",
758 | "lineWidth": 1,
759 | "pointSize": 5,
760 | "scaleDistribution": {
761 | "type": "linear"
762 | },
763 | "showPoints": "never",
764 | "spanNulls": false,
765 | "stacking": {
766 | "group": "A",
767 | "mode": "normal"
768 | },
769 | "thresholdsStyle": {
770 | "mode": "off"
771 | }
772 | },
773 | "mappings": [],
774 | "min": 0,
775 | "thresholds": {
776 | "mode": "absolute",
777 | "steps": [
778 | {
779 | "color": "green",
780 | "value": null
781 | },
782 | {
783 | "color": "red",
784 | "value": 80
785 | }
786 | ]
787 | },
788 | "unit": "bytes"
789 | },
790 | "overrides": []
791 | },
792 | "gridPos": {
793 | "h": 9,
794 | "w": 12,
795 | "x": 12,
796 | "y": 24
797 | },
798 | "id": 164,
799 | "links": [],
800 | "options": {
801 | "legend": {
802 | "calcs": [],
803 | "displayMode": "list",
804 | "placement": "bottom",
805 | "showLegend": true
806 | },
807 | "tooltip": {
808 | "mode": "multi",
809 | "sort": "none"
810 | }
811 | },
812 | "pluginVersion": "9.5.2",
813 | "targets": [
814 | {
815 | "datasource": {
816 | "uid": "$datasource"
817 | },
818 | "editorMode": "code",
819 | "expr": "(\n node_memory_MemTotal_bytes{job=\"node-exporter\"}\n-\n node_memory_MemFree_bytes{job=\"node-exporter\"}\n-\n node_memory_Buffers_bytes{job=\"node-exporter\"}\n-\n node_memory_Cached_bytes{job=\"node-exporter\"}\n)\n",
820 | "format": "time_series",
821 | "intervalFactor": 2,
822 | "legendFormat": "memory used",
823 | "range": true,
824 | "refId": "A"
825 | },
826 | {
827 | "datasource": {
828 | "uid": "$datasource"
829 | },
830 | "editorMode": "code",
831 | "expr": "node_memory_Buffers_bytes{job=\"node-exporter\"}",
832 | "format": "time_series",
833 | "intervalFactor": 2,
834 | "legendFormat": "memory buffers",
835 | "range": true,
836 | "refId": "B"
837 | },
838 | {
839 | "datasource": {
840 | "uid": "$datasource"
841 | },
842 | "editorMode": "code",
843 | "expr": "node_memory_Cached_bytes{job=\"node-exporter\"}",
844 | "format": "time_series",
845 | "intervalFactor": 2,
846 | "legendFormat": "memory cached",
847 | "range": true,
848 | "refId": "C"
849 | },
850 | {
851 | "datasource": {
852 | "uid": "$datasource"
853 | },
854 | "editorMode": "code",
855 | "expr": "node_memory_MemFree_bytes{job=\"node-exporter\"}",
856 | "format": "time_series",
857 | "intervalFactor": 2,
858 | "legendFormat": "memory free",
859 | "range": true,
860 | "refId": "D"
861 | }
862 | ],
863 | "title": "Cluster Memory Usage",
864 | "transparent": true,
865 | "type": "timeseries"
866 | },
867 | {
868 | "aliasColors": {},
869 | "bars": false,
870 | "dashLength": 10,
871 | "dashes": false,
872 | "datasource": {
873 | "type": "prometheus",
874 | "uid": "prometheus"
875 | },
876 | "fill": 0,
877 | "fillGradient": 0,
878 | "gridPos": {
879 | "h": 9,
880 | "w": 8,
881 | "x": 0,
882 | "y": 33
883 | },
884 | "hiddenSeries": false,
885 | "id": 165,
886 | "legend": {
887 | "alignAsTable": false,
888 | "avg": false,
889 | "current": false,
890 | "max": false,
891 | "min": false,
892 | "rightSide": false,
893 | "show": true,
894 | "total": false,
895 | "values": false
896 | },
897 | "lines": true,
898 | "linewidth": 1,
899 | "links": [],
900 | "nullPointMode": "null",
901 | "options": {
902 | "alertThreshold": true
903 | },
904 | "percentage": false,
905 | "pluginVersion": "9.5.2",
906 | "pointradius": 5,
907 | "points": false,
908 | "renderer": "flot",
909 | "seriesOverrides": [
910 | {
911 | "alias": "/ read| written/",
912 | "yaxis": 1
913 | },
914 | {
915 | "alias": "/ io time/",
916 | "yaxis": 2
917 | }
918 | ],
919 | "spaceLength": 10,
920 | "stack": false,
921 | "steppedLine": false,
922 | "targets": [
923 | {
924 | "datasource": {
925 | "uid": "$datasource"
926 | },
927 | "editorMode": "code",
928 | "expr": "rate(node_disk_read_bytes_total{job=\"node-exporter\", device=~\"(/dev/)?(mmcblk.p.+|nvme.+|rbd.+|sd.+|vd.+|xvd.+|dm-.+|md.+|dasd.+)\"}[$__rate_interval])",
929 | "format": "time_series",
930 | "intervalFactor": 1,
931 | "legendFormat": "{{device}} read",
932 | "range": true,
933 | "refId": "A"
934 | },
935 | {
936 | "datasource": {
937 | "uid": "$datasource"
938 | },
939 | "editorMode": "code",
940 | "expr": "rate(node_disk_written_bytes_total{job=\"node-exporter\", device=~\"(/dev/)?(mmcblk.p.+|nvme.+|rbd.+|sd.+|vd.+|xvd.+|dm-.+|md.+|dasd.+)\"}[$__rate_interval])",
941 | "format": "time_series",
942 | "intervalFactor": 1,
943 | "legendFormat": "{{device}} written",
944 | "range": true,
945 | "refId": "B"
946 | },
947 | {
948 | "datasource": {
949 | "uid": "$datasource"
950 | },
951 | "editorMode": "code",
952 | "expr": "rate(node_disk_io_time_seconds_total{job=\"node-exporter\", device=~\"(/dev/)?(mmcblk.p.+|nvme.+|rbd.+|sd.+|vd.+|xvd.+|dm-.+|md.+|dasd.+)\"}[$__rate_interval])",
953 | "format": "time_series",
954 | "intervalFactor": 1,
955 | "legendFormat": "{{device}} io time",
956 | "range": true,
957 | "refId": "C"
958 | }
959 | ],
960 | "thresholds": [],
961 | "timeRegions": [],
962 | "title": "Disk I/O",
963 | "tooltip": {
964 | "shared": true,
965 | "sort": 0,
966 | "value_type": "individual"
967 | },
968 | "transparent": true,
969 | "type": "graph",
970 | "xaxis": {
971 | "mode": "time",
972 | "show": true,
973 | "values": []
974 | },
975 | "yaxes": [
976 | {
977 | "format": "Bps",
978 | "logBase": 1,
979 | "show": true
980 | },
981 | {
982 | "format": "percentunit",
983 | "logBase": 1,
984 | "show": true
985 | }
986 | ],
987 | "yaxis": {
988 | "align": false
989 | }
990 | },
991 | {
992 | "aliasColors": {},
993 | "bars": false,
994 | "dashLength": 10,
995 | "dashes": false,
996 | "datasource": {
997 | "type": "prometheus",
998 | "uid": "prometheus"
999 | },
1000 | "description": "Network received (bits/s)",
1001 | "fill": 0,
1002 | "fillGradient": 0,
1003 | "gridPos": {
1004 | "h": 9,
1005 | "w": 8,
1006 | "x": 8,
1007 | "y": 33
1008 | },
1009 | "hiddenSeries": false,
1010 | "id": 166,
1011 | "legend": {
1012 | "alignAsTable": false,
1013 | "avg": false,
1014 | "current": false,
1015 | "max": false,
1016 | "min": false,
1017 | "rightSide": false,
1018 | "show": true,
1019 | "total": false,
1020 | "values": false
1021 | },
1022 | "lines": true,
1023 | "linewidth": 1,
1024 | "links": [],
1025 | "nullPointMode": "null",
1026 | "options": {
1027 | "alertThreshold": true
1028 | },
1029 | "percentage": false,
1030 | "pluginVersion": "9.5.2",
1031 | "pointradius": 5,
1032 | "points": false,
1033 | "renderer": "flot",
1034 | "seriesOverrides": [],
1035 | "spaceLength": 10,
1036 | "stack": false,
1037 | "steppedLine": false,
1038 | "targets": [
1039 | {
1040 | "datasource": {
1041 | "uid": "$datasource"
1042 | },
1043 | "editorMode": "code",
1044 | "expr": "rate(node_network_receive_bytes_total{job=\"node-exporter\", device!=\"lo\"}[$__rate_interval]) * 8",
1045 | "format": "time_series",
1046 | "intervalFactor": 1,
1047 | "legendFormat": "{{device}}",
1048 | "range": true,
1049 | "refId": "A"
1050 | }
1051 | ],
1052 | "thresholds": [],
1053 | "timeRegions": [],
1054 | "title": "Network Received",
1055 | "tooltip": {
1056 | "shared": true,
1057 | "sort": 0,
1058 | "value_type": "individual"
1059 | },
1060 | "transparent": true,
1061 | "type": "graph",
1062 | "xaxis": {
1063 | "mode": "time",
1064 | "show": true,
1065 | "values": []
1066 | },
1067 | "yaxes": [
1068 | {
1069 | "format": "bps",
1070 | "logBase": 1,
1071 | "min": 0,
1072 | "show": true
1073 | },
1074 | {
1075 | "format": "bps",
1076 | "logBase": 1,
1077 | "min": 0,
1078 | "show": true
1079 | }
1080 | ],
1081 | "yaxis": {
1082 | "align": false
1083 | }
1084 | },
1085 | {
1086 | "aliasColors": {},
1087 | "bars": false,
1088 | "dashLength": 10,
1089 | "dashes": false,
1090 | "datasource": {
1091 | "type": "prometheus",
1092 | "uid": "prometheus"
1093 | },
1094 | "description": "Network transmitted (bits/s)",
1095 | "fill": 0,
1096 | "fillGradient": 0,
1097 | "gridPos": {
1098 | "h": 9,
1099 | "w": 8,
1100 | "x": 16,
1101 | "y": 33
1102 | },
1103 | "hiddenSeries": false,
1104 | "id": 167,
1105 | "legend": {
1106 | "alignAsTable": false,
1107 | "avg": false,
1108 | "current": false,
1109 | "max": false,
1110 | "min": false,
1111 | "rightSide": false,
1112 | "show": true,
1113 | "total": false,
1114 | "values": false
1115 | },
1116 | "lines": true,
1117 | "linewidth": 1,
1118 | "links": [],
1119 | "nullPointMode": "null",
1120 | "options": {
1121 | "alertThreshold": true
1122 | },
1123 | "percentage": false,
1124 | "pluginVersion": "9.5.2",
1125 | "pointradius": 5,
1126 | "points": false,
1127 | "renderer": "flot",
1128 | "seriesOverrides": [],
1129 | "spaceLength": 10,
1130 | "stack": false,
1131 | "steppedLine": false,
1132 | "targets": [
1133 | {
1134 | "datasource": {
1135 | "uid": "$datasource"
1136 | },
1137 | "editorMode": "code",
1138 | "expr": "rate(node_network_transmit_bytes_total{job=\"node-exporter\", device!=\"lo\"}[$__rate_interval]) * 8",
1139 | "format": "time_series",
1140 | "intervalFactor": 1,
1141 | "legendFormat": "{{device}}",
1142 | "range": true,
1143 | "refId": "A"
1144 | }
1145 | ],
1146 | "thresholds": [],
1147 | "timeRegions": [],
1148 | "title": "Network Transmitted",
1149 | "tooltip": {
1150 | "shared": true,
1151 | "sort": 0,
1152 | "value_type": "individual"
1153 | },
1154 | "transparent": true,
1155 | "type": "graph",
1156 | "xaxis": {
1157 | "mode": "time",
1158 | "show": true,
1159 | "values": []
1160 | },
1161 | "yaxes": [
1162 | {
1163 | "format": "bps",
1164 | "logBase": 1,
1165 | "min": 0,
1166 | "show": true
1167 | },
1168 | {
1169 | "format": "bps",
1170 | "logBase": 1,
1171 | "min": 0,
1172 | "show": true
1173 | }
1174 | ],
1175 | "yaxis": {
1176 | "align": false
1177 | }
1178 | }]
1179 |
--------------------------------------------------------------------------------
/server/routes/deleteRoute.ts:
--------------------------------------------------------------------------------
1 | import express, { Router, Request, Response } from 'express';
2 | const loginRouter: Router = express.Router();
3 |
4 | import userController from '../controllers/userController';
5 | import cookieController from '../controllers/cookieController';
6 | import sessionController from '../controllers/sessionController';
7 |
8 | loginRouter.post(
9 | '/',
10 | userController.verifyUser,
11 | userController.deleteUser,
12 | (req: Request, res: Response) => {
13 | return res.status(201).json('Deleted');
14 | }
15 | );
16 |
17 | export default loginRouter;
18 |
--------------------------------------------------------------------------------
/server/routes/loginRoute.ts:
--------------------------------------------------------------------------------
1 | import express, { Router, Request, Response } from 'express';
2 | const loginRouter: Router = express.Router();
3 |
4 | import userController from '../controllers/userController';
5 | import cookieController from '../controllers/cookieController';
6 | import sessionController from '../controllers/sessionController';
7 | import grafanaController from '../controllers/grafanaController';
8 | import installController from '../controllers/installController';
9 |
10 | loginRouter.post(
11 | '/',
12 | userController.verifyUser,
13 | installController.promInstall,
14 | installController.grafEmbed,
15 | installController.portForward,
16 | grafanaController.createDashboard,
17 | userController.addUrls,
18 | sessionController.startSession,
19 | cookieController.setSSIDCookie,
20 | (req: Request, res: Response) => {
21 | return res.status(201).json(res.locals.URLS);
22 | }
23 | );
24 |
25 | export default loginRouter;
26 |
--------------------------------------------------------------------------------
/server/routes/signUpRoute.ts:
--------------------------------------------------------------------------------
1 | import express, { Request, Response } from 'express';
2 | const signUpRouter = express.Router();
3 |
4 | import grafanaController from '../controllers/grafanaController';
5 | import userController from '../controllers/userController';
6 | import cookieController from '../controllers/cookieController';
7 | import sessionController from '../controllers/sessionController';
8 |
9 | signUpRouter.post(
10 | '/',
11 | userController.createUser,
12 | (req: Request, res: Response) => {
13 | return res.status(201).json({ Success: 'User Created!'});
14 | }
15 | );
16 |
17 | export default signUpRouter;
18 |
--------------------------------------------------------------------------------
/server/server.ts:
--------------------------------------------------------------------------------
1 | import express, { Request, Response, NextFunction } from 'express';
2 | import mongoose, { ConnectOptions } from 'mongoose';
3 | import cookieParser from 'cookie-parser';
4 | import cors from 'cors';
5 | import dotenv from 'dotenv';
6 |
7 | dotenv.config();
8 |
9 | const port: number = 5050;
10 |
11 | const app = express();
12 |
13 | app.use(express.json());
14 | app.use(cookieParser());
15 | app.use(cors());
16 |
17 | import loginRouter from './routes/loginRoute';
18 | import signUpRouter from './routes/signUpRoute';
19 | import deleteRouter from './routes/deleteRoute';
20 | import installController from './controllers/installController';
21 |
22 | const mongoURI = process.env.MONGO_URI;
23 |
24 | mongoose.connect(mongoURI, {
25 | useNewUrlParser: true,
26 | useUnifiedTopology: true,
27 | } as ConnectOptions);
28 |
29 | // redirect to routes
30 | app.use('/login', loginRouter);
31 | app.use('/signup', signUpRouter);
32 | app.use('/delete', deleteRouter);
33 |
34 | app.get('/killPort', installController.killPort, (req,res) => res.sendStatus(200));
35 |
36 |
37 | app.use((req: Request, res: Response) =>
38 | res.status(404).send('Page Not Found')
39 | );
40 |
41 | app.use(
42 | (
43 | err: { log?: string; status?: number; message?: { err: string } },
44 | req: Request,
45 | res: Response,
46 | next: NextFunction
47 | ) => {
48 | const defaultErr: {
49 | log: string;
50 | status: number;
51 | message: { err: string };
52 | } = {
53 | log: 'Express error handler caught unknown middleware error',
54 | status: 400,
55 | message: { err: 'An error occurred' },
56 | };
57 | const errorObj = Object.assign({}, defaultErr, err);
58 | console.log(errorObj.log);
59 | return res.status(errorObj.status).json(errorObj.message);
60 | }
61 | );
62 |
63 | app.listen(port, () => {
64 | console.log(`Listening on port ${port}...`);
65 | });
66 |
67 | export default app;
--------------------------------------------------------------------------------
/test-setup.js:
--------------------------------------------------------------------------------
1 | import 'whatwg-fetch';
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react",
4 | "module": "CommonJS",
5 | "esModuleInterop": true,
6 | "allowSyntheticDefaultImports": true,
7 | "noImplicitAny": true,
8 | "noEmit": false,
9 | "resolveJsonModule": true
10 | },
11 | "include": ["client/**/*", "server/**/*"],
12 | "exclude": ["dist", "node_modules"]
13 | }
14 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
4 |
5 | module.exports = {
6 | mode: 'development',
7 | entry: path.resolve(__dirname, './client/index.js'),
8 |
9 | output: {
10 | filename: 'bundle.js',
11 | path: path.resolve(__dirname, 'dist'),
12 | },
13 |
14 | target: 'web',
15 | devtool: 'eval-source-map',
16 |
17 | devServer: {
18 | host: 'localhost',
19 | port: 8080,
20 | hot: true,
21 | historyApiFallback: true,
22 |
23 | headers: { 'Access-Control-Allow-Origin': '*' },
24 | proxy: {
25 | '/static/**': {
26 | target: 'http://localhost:5050/',
27 | },
28 | '/login/**': {
29 | target: 'http://localhost:5050/',
30 | },
31 | '/signup/**': {
32 | target: 'http://localhost:5050/',
33 | },
34 | '/main/**': {
35 | target: 'http://localhost:5050/',
36 | },
37 | '/install/**': {
38 | target: 'http://localhost:5050/',
39 | },
40 | },
41 | },
42 |
43 | resolve: {
44 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
45 | },
46 |
47 | plugins: [
48 | new MiniCssExtractPlugin(),
49 | new HtmlWebpackPlugin({
50 | filename: 'index.html',
51 | template: path.resolve(__dirname, './public/index.html'),
52 | }),
53 | ],
54 |
55 | module: {
56 | rules: [
57 | {
58 | test: /\.jsx?/,
59 | exclude: /(node_modules)/,
60 | use: {
61 | loader: 'babel-loader',
62 | options: {
63 | presets: ['@babel/preset-env', '@babel/preset-react'],
64 | },
65 | },
66 | },
67 | {
68 | test: /\.(ts|tsx)$/,
69 | exclude: /node_modules/,
70 | use: ['ts-loader'],
71 | },
72 | {
73 | test: /\.s[ac]ss$/i,
74 | exclude: /node_modules/,
75 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
76 | },
77 | {
78 | test: /\.(png|jpe?g|gif)$/i,
79 | use: [
80 | {
81 | loader: 'file-loader',
82 | },
83 | ],
84 | },
85 | ],
86 | },
87 | };
88 |
--------------------------------------------------------------------------------