├── .eslintrc.js
├── .gitignore
├── .prettierignore
├── DEV_README.md
├── LICENSE
├── Prism logo 1.png
├── README.md
├── __tests__
├── Login.test.tsx
└── backend_tests
│ ├── loginRoutes.ts
│ ├── metricsRoutes.ts
│ └── userController.ts
├── babel.config.cjs
├── babelrc.js
├── client
├── assets
│ ├── In-Blue-96.png
│ ├── In-Blue-96.png:Zone.Identifier
│ ├── In-White-96.png
│ ├── In-White-96.png:Zone.Identifier
│ ├── Screenshot 2023-08-08 214009.png:Zone.Identifier
│ ├── Screenshot 2023-08-08 214025.png:Zone.Identifier
│ ├── Screenshot 2023-08-08 214053.png
│ ├── Screenshot 2023-08-08 214053.png:Zone.Identifier
│ ├── Screenshot 2023-08-08 214108.png
│ ├── Screenshot 2023-08-08 214108.png:Zone.Identifier
│ ├── Screenshot 2023-08-08 214145.png:Zone.Identifier
│ ├── Screenshot 2023-08-08 214211.png:Zone.Identifier
│ ├── Screenshot 2023-08-08 214238.png
│ ├── Screenshot 2023-08-08 214238.png:Zone.Identifier
│ ├── Screenshot 2023-08-08 223451.png:Zone.Identifier
│ ├── Screenshot 2023-08-08 223511.png:Zone.Identifier
│ ├── Screenshot 2023-08-08 223650.png:Zone.Identifier
│ ├── Screenshot 2023-08-08 223707.png:Zone.Identifier
│ ├── github-mark-white.png
│ ├── github-mark-white.png:Zone.Identifier
│ ├── github-mark.png
│ ├── github-mark.png:Zone.Identifier
│ ├── github-mark.svg
│ ├── github-mark.svg:Zone.Identifier
│ ├── icons8-twitter.svg
│ ├── icons8-twitter.svg:Zone.Identifier
│ ├── logo-twitter-png-40404.png
│ ├── logo-twitter-png-40404.png:Zone.Identifier
│ ├── prismlogo.png
│ ├── prismlogodarkmode.png
│ ├── selected_1_Dark.png
│ ├── selected_1_Light.png
│ ├── selected_2.png
│ ├── selected_3.png
│ ├── selected_4_dark.png
│ ├── selected_4_light.png
│ ├── selected_5_dark.png
│ └── selected_5_light.png
├── components
│ ├── App.tsx
│ ├── ClusterView
│ │ ├── ClusterViewHeader
│ │ │ ├── ClusterViewHeader.tsx
│ │ │ ├── LighDarkMode.tsx
│ │ │ ├── Profile.tsx
│ │ │ └── themeContext.tsx
│ │ ├── Dashboard.tsx
│ │ ├── Dashboard
│ │ │ ├── ClusterMap.tsx
│ │ │ ├── NodesView.tsx
│ │ │ ├── Overview.tsx
│ │ │ ├── PodsView.tsx
│ │ │ └── mvp_dashboard.json
│ │ └── SidePanel
│ │ │ ├── Navigation.tsx
│ │ │ └── SidePanel.tsx
│ ├── LandingPage
│ │ ├── LandingFooter.tsx
│ │ ├── LandingHeader.tsx
│ │ ├── LandingMain.tsx
│ │ └── LandingPage.tsx
│ ├── Login.tsx
│ ├── Main
│ │ ├── dashboard
│ │ │ ├── clusterView.jsx
│ │ │ ├── dashboard.jsx
│ │ │ ├── nodesView.jsx
│ │ │ ├── overview.jsx
│ │ │ └── podsView.jsx
│ │ ├── header
│ │ │ ├── lighDarkMode.jsx
│ │ │ ├── mainHeader.jsx
│ │ │ └── profile.jsx
│ │ ├── leftPanel.jsx
│ │ └── main.jsx
│ └── Signup.tsx
├── index.tsx
├── styles.css
└── styles_output.css
├── coverage
└── coverage-summary.json
├── custom-test-env.js
├── grafana
├── api_token.json
├── config_values.yaml
└── dashboards
│ └── mvp_dashboard.json
├── index.html
├── jest.config.json
├── package-lock.json
├── package.json
├── postcss.config.js
├── prism_logo_square.png
├── readme-gifs
├── demo.gif
├── demo_darkmode.gif
├── demo_login.gif
├── demo_signup.gif
└── demo_views.gif
├── server
├── controllers
│ ├── metricsController.ts
│ └── userController.ts
├── db
│ ├── db.ts
│ └── models
│ │ └── userSchema.ts
├── routers
│ ├── apiRouter.ts
│ └── userRouter.ts
└── server.ts
├── startup.zsh
├── tailwind.config.js
├── tsconfig.json
├── types
├── index.d.ts
└── types.ts
└── webpack.config.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "es2021": true
5 | },
6 | "extends": [
7 | "eslint:recommended",
8 | "plugin:@typescript-eslint/recommended",
9 | "plugin:react/recommended"
10 | ],
11 | "overrides": [
12 | {
13 | "env": {
14 | "node": true
15 | },
16 | "files": [
17 | ".eslintrc.{js,cjs}"
18 | ],
19 | "parserOptions": {
20 | "sourceType": "script"
21 | }
22 | }
23 | ],
24 | "parser": "@typescript-eslint/parser",
25 | "parserOptions": {
26 | "ecmaVersion": "latest",
27 | "sourceType": "module"
28 | },
29 | "plugins": [
30 | "@typescript-eslint",
31 | "react"
32 | ],
33 | "rules": {
34 | "react/prop-types": "off"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # if you need the .env file see our OSP slack!
2 | node_modules
3 | .env
4 | grafana/api_token.json
5 | build
6 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | client/components/app.jsx
--------------------------------------------------------------------------------
/DEV_README.md:
--------------------------------------------------------------------------------
1 | # A Presumptive Thank You
2 | We're so excited that you're thinking about contributing to our application! We built this thinking of the many great directions in which it could go and are very grateful to those who would take it upon themselves to help.
3 |
4 | # Possible Directions for Future Work
5 |
6 | ## ⚛️ State management and Database Optimization
7 | Proper authentication and caching would increase security and enhance the user experience. This will require more comprehensive state management and a close look at what might be best to save in order to persist between reloads.
8 |
9 | Additionally, database caching of the dashboard panel URLs would also easily allow a mobile application to become feasible.
10 |
11 | ## Customizable dashboards
12 | It would make for an even better UX if users were able to freely resize and move dashboards in a way that would save in state (possibly persist through the database).
13 |
14 | ## 🃏 Test Coverage
15 | The current level of test coverage is very limited to route unit tests and a few key react components.
16 | Room for improvement includes:
17 | - [ ] Unit testing for middleware controllers
18 | - [ ] Route testing could be updated to include token authentication
19 | - [ ] State management integration testing
20 |
21 | ## 🚨 Health Alert Notifications
22 | In addition to allowing the user to view health, it would be useful for the application to be able to send notifications (could be on Slack or a mobile app notification) if something is wrong. This should be achievable using [Grafana's API](https://grafana.com/docs/grafana/latest/alerting/set-up/).
23 |
24 | ## 🛜 Inter-Cluster Network Views
25 | At a level above a single cluster, we think it would be of user benefit to be able to view relationships among more than one running cluster.
26 | This is perhaps the most ambitious stretch feature in its requirement to extend our application's understanding of the configuration of and access to one cluster to multiple, but it's also one of the most exciting!
27 |
28 | # File structure
29 | - [**client**](client)
30 | - [**assets**](client/assets)
31 | - [**components**](client/components)
32 | - [**ClusterView**](client/components/ClusterView)
33 | - [**ClusterViewHeader**](client/components/ClusterView/ClusterViewHeader)
34 | - [**Dashboard**](client/components/ClusterView/Dashboard)
35 | - [**SidePanel**](client/components/ClusterView/SidePanel)
36 | - [**LandingPage**](client/components/LandingPage)
37 | - [**Main**](client/components/Main)
38 | - [**dashboard**](client/components/Main/dashboard)
39 | - [**header**](client/components/Main/header)
40 | - [**coverage**](coverage)
41 | - [**grafana**](grafana)
42 | - [**dashboards**](grafana/dashboards)
43 | - [**server**](server)
44 | - [**controllers**](server/controllers)
45 | - [**db**](server/db)
46 | - [**models**](server/db/models)
47 | - [**routers**](server/routers)
48 | - [**types**](types)
49 |
50 |
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 OSLabs Beta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Prism logo 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/Prism logo 1.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 
4 | 
5 | 
6 | 
7 | 
8 | 
9 | 
10 | 
11 | 
12 | 
13 | 
14 | 
15 | 
16 | 
17 | 
18 |
19 |
20 |
21 | # Introducing Prism!
22 |
23 | Prism is a Kubernetes and Docker visualizer that helps users understand the status of and relationships between their nodes, pods, services, and containers. It will help developers quickly view their server status and identify problem areas with live metrics and pod health statistics.
24 | Our goal for this project was to provide the best possible user experience while minimizing the code a user has to write (everything launches with a single command).
25 |
26 | ## Get insights into your Kubernetes clusters and Docker containers
27 |
28 |
29 |
30 | ## Secure, and built for you!
31 |
32 | Login to Prism to see your clusters, nodes, and pods automatically. And with a dark/light mode, you can enjoy it how you want.
33 |
34 |
35 |
36 | ## Features
37 |
38 | | Feature | Status |
39 | | ----------------------------------------------------------------- | ------ |
40 | | Prometheus and Grafana Intergration | ✅ |
41 | | Custom Dashboard | ✅ |
42 | | an Overview, Pods view and Node view of metrics | ✅ |
43 | | SASS and Tailwind CSS | ✅ |
44 | | Typescript conversion | ✅ |
45 | | Testing (React Testing Library/Jest front-end, Supertest backend) | ⏳ |
46 | | Fully intergrated OAuth/User authentication | ⏳ |
47 | | Customizable Dashboards | 🙏🏻 |
48 | | Historcial Data and Trends | 🙏🏻 |
49 |
50 | Done = ✅
51 |
52 | In Progress = ⏳
53 |
54 | Looking for contributors = 🙏🏻
55 |
56 | ## Getting Started
57 |
58 | ### Requirements
59 |
60 | - [ ] Running cluster in Kubernetes/ Minikube
61 | - [ ] The following ports must be free:
62 | - [ ] 8080 ( where the application will be located)
63 | - [ ] 3333 (used by the backend of the application)
64 | - [ ] 3000 (used by Grafana)
65 |
66 | ### Steps :
67 |
68 | - [ ] Fork the repository and clone to your local machine
69 | - [ ] Set up authentication: Create a `.env` file in the root directory with the following:
70 | - [ ] (optional) Private auth database: Your MongoDB URI (key `MONGO_URI`)
71 | - [ ] (optional) GitHub OAuth: A client ID and secret key (keys `CLIENT_ID`, `CLIENT_SECRET`)
72 | - [Read more from GitHub about setting up oAuth in your settings](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
73 | - [ ] A secret of your choice for json web tokens (key `JWT_SECRET`)
74 | - [ ] Execute the startup shell script (run `./startup.zsh`) - this will:
75 |
76 | - [ ] Install necessary dependencies for the web application
77 | - [ ] Install Prometheus 🔥 and Grafana 📊 onto your cluster with our custom configuration
78 | - [ ] Start up the web application
79 |
80 | - [ ] Go to `http://localhost:8080` and view metrics to your heart's desire 🤩
81 |
82 | ### Enjoy Prism!
83 |
84 | Once you've done the steps above you'll be able to quickly view live metrics and pod health statistics with ease.
85 |
86 |
87 |
88 | ## Contribute to the project
89 |
90 | - View our [Contributor README](/DEV_README.md)
91 |
92 | ## Read More
93 |
94 | [Check out our article on Medium!](https://medium.com/@k8s.prism/prism-all-in-one-kubernetes-visualizer-7338b56f8de2)
95 |
96 | ## Authors
97 |
98 | - list of all people and our links
99 | - [Beserat Tafesse](https://github.com/BeseratT)
100 | - [Dawit Merid](https://github.com/dawitmerid)
101 | - [James Li](https://github.com/Jxmes-Li)
102 | - [Josh Hall](https://github.com/joshuarhall)
103 | - [Paul Glenn](https://github.com/paglenn)
104 |
--------------------------------------------------------------------------------
/__tests__/Login.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import '@testing-library/jest-dom/extend-expect';
4 | import Login from '../client/components/Login';
5 | import { beforeEach, describe, expect, jest, test } from '@jest/globals';
6 | import { useNavigate } from 'react-router';
7 | // to import Jest and mocking useNavigate
8 | const mockUsedNavigate = jest.fn();
9 |
10 | jest.mock('react-router-dom', () => ({
11 | useNavigate: () => mockUsedNavigate,
12 | }));
13 |
14 | // const useNavigateMock = () => {
15 | // return mockUsedNavigate
16 | // }
17 |
18 | // jest.mock('react-router-dom', () => {
19 | // useNavigate: () => mockUsedNavigate
20 | // })
21 |
22 | // Renders Login component before each test
23 | beforeEach(() => {
24 | render( );
25 | });
26 |
27 | test('If it renders', () => {
28 | const ha = screen.getByText(/Login Form/);
29 | expect(ha).toBeInTheDocument();
30 | });
31 |
32 | test('If username and password input bars render', () => {
33 | const username = screen.getByRole('textbox', {
34 | name: 'username',
35 | });
36 | const password = screen.getByLabelText(/password/);
37 | expect(username).toBeInTheDocument();
38 | expect(password).toBeInTheDocument();
39 | });
40 |
41 | test('If clicking close goes to dashboard', () => {});
42 | // function beforeEach(arg0: () => void) {
43 | // throw new Error("Function not implemented.");
44 | // }
45 |
--------------------------------------------------------------------------------
/__tests__/backend_tests/loginRoutes.ts:
--------------------------------------------------------------------------------
1 | import request from 'supertest';
2 | const userRoute: string = 'http://localhost:3333/user';
3 |
4 | describe('Signup route tests', () => {
5 | const username: string = `Test ${Date.now()}`;
6 | it('should respond with username and a 201 status on successful creation', () => {
7 | return request(userRoute)
8 | .post('/signup')
9 | .send({ username: username, password: 'password' })
10 | .expect(201)
11 | .then((response) => {
12 | expect(response.body.username).toBe(username);
13 | expect(response.body.created).toBe(true);
14 | });
15 | });
16 |
17 | it('should not create a duplicate user', () => {
18 | return request(userRoute)
19 | .post('/signup')
20 | .send({ username: username, password: 'password' })
21 | .expect(202)
22 | .then((response) => {
23 | expect(response.body.created).toBe(false);
24 | });
25 | });
26 | });
27 |
28 | // integration test for login route
29 | describe('login route tests', () => {
30 | it('should respond with a 200 status code for successful login', () => {
31 | return request(userRoute)
32 | .post('/login')
33 | .send({ username: 'Test', password: 'password' })
34 | .expect(200)
35 | .then((response) => {
36 | expect(response.body.auth).toBe(true);
37 | });
38 | });
39 |
40 | it('should respond with a 401 status code for unsuccessful login', () => {
41 | return request(userRoute)
42 | .post('/login')
43 | .send({ username: 'Test', password: 'test' })
44 | .expect(401)
45 | .then((response) => expect(response.body.auth).toBe(false));
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/__tests__/backend_tests/metricsRoutes.ts:
--------------------------------------------------------------------------------
1 | // Backend route integration tests
2 | import request from 'supertest';
3 | const serverRoute: string = 'http://localhost:3333/api';
4 |
5 | xdescribe('Dashboard creation route', () => {
6 | it('responds with dashboard url', () => {
7 | return request(serverRoute)
8 | .post('/')
9 | .then((response) => {
10 | console.log(response.body);
11 | expect(response.body.frameURL).toBeTruthy();
12 | });
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/__tests__/backend_tests/userController.ts:
--------------------------------------------------------------------------------
1 | //import userController from '../../server/controllers/userController';
2 | import { Request, Response, NextFunction } from 'express';
3 |
4 | // Typing: Request and response body are objects
5 |
6 | const next = jest.fn() as NextFunction;
7 | const request = {
8 | body: {},
9 | } as Request;
10 |
11 | const response = {
12 | locals: {},
13 | } as Response;
14 |
15 | // test createUser
16 | // xdescribe('user controller authentication', () => {
17 | // beforeEach(() => {
18 | // response.locals.user = {};
19 | // (request.body.username = 'test'), (request.body.password = 'password');
20 | // });
21 |
22 | // test('Authentication sets properties correctly for true password ', async () => {
23 | // await userController.authUser(request, response, next);
24 | // expect(response.locals.user.username).toEqual(request.body.username);
25 | // expect(response.locals.user.auth).toBe(true);
26 | // });
27 | // });
28 |
--------------------------------------------------------------------------------
/babel.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-react',
5 | {
6 | runtime: 'automatic',
7 | },
8 | ],
9 | '@babel/preset-env',
10 | '@babel/preset-typescript',
11 | ],
12 | };
13 |
--------------------------------------------------------------------------------
/babelrc.js:
--------------------------------------------------------------------------------
1 | export default {
2 | presets: [['@babel/preset-env', { modules: false }]],
3 | // The rest is the same ...
4 | };
5 |
--------------------------------------------------------------------------------
/client/assets/In-Blue-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/In-Blue-96.png
--------------------------------------------------------------------------------
/client/assets/In-Blue-96.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | ZoneId=3
3 | ReferrerUrl=C:\Users\dawit\OneDrive\Desktop\BIBI\Logos\linkedin-logos.zip
4 |
--------------------------------------------------------------------------------
/client/assets/In-White-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/In-White-96.png
--------------------------------------------------------------------------------
/client/assets/In-White-96.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | ZoneId=3
3 | ReferrerUrl=C:\Users\dawit\OneDrive\Desktop\BIBI\Logos\linkedin-logos.zip
4 |
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 214009.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe
3 | ZoneId=3
4 |
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 214025.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe
3 | ZoneId=3
4 |
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 214053.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/Screenshot 2023-08-08 214053.png
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 214053.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe
3 | ZoneId=3
4 |
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 214108.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/Screenshot 2023-08-08 214108.png
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 214108.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe
3 | ZoneId=3
4 |
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 214145.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe
3 | ZoneId=3
4 |
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 214211.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe
3 | ZoneId=3
4 |
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 214238.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/Screenshot 2023-08-08 214238.png
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 214238.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe
3 | ZoneId=3
4 |
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 223451.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe
3 | ZoneId=3
4 |
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 223511.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe
3 | ZoneId=3
4 |
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 223650.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe
3 | ZoneId=3
4 |
--------------------------------------------------------------------------------
/client/assets/Screenshot 2023-08-08 223707.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe
3 | ZoneId=3
4 |
--------------------------------------------------------------------------------
/client/assets/github-mark-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/github-mark-white.png
--------------------------------------------------------------------------------
/client/assets/github-mark-white.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | ZoneId=3
3 | ReferrerUrl=C:\Users\dawit\OneDrive\Desktop\BIBI\Logos\github-mark (1).zip
4 |
--------------------------------------------------------------------------------
/client/assets/github-mark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/github-mark.png
--------------------------------------------------------------------------------
/client/assets/github-mark.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | ZoneId=3
3 | ReferrerUrl=C:\Users\dawit\OneDrive\Desktop\BIBI\Logos\github-mark (1).zip
4 |
--------------------------------------------------------------------------------
/client/assets/github-mark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/assets/github-mark.svg:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | ZoneId=3
3 | ReferrerUrl=C:\Users\dawit\OneDrive\Desktop\BIBI\Logos\github-mark (1).zip
4 |
--------------------------------------------------------------------------------
/client/assets/icons8-twitter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/assets/icons8-twitter.svg:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | ZoneId=3
3 | HostUrl=about:internet
4 |
--------------------------------------------------------------------------------
/client/assets/logo-twitter-png-40404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/logo-twitter-png-40404.png
--------------------------------------------------------------------------------
/client/assets/logo-twitter-png-40404.png:Zone.Identifier:
--------------------------------------------------------------------------------
1 | [ZoneTransfer]
2 | ZoneId=3
3 | ReferrerUrl=https://www.freepnglogos.com/images/logo-twitter-png-40404.html
4 | HostUrl=https://www.freepnglogos.com/download/40404
5 |
--------------------------------------------------------------------------------
/client/assets/prismlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/prismlogo.png
--------------------------------------------------------------------------------
/client/assets/prismlogodarkmode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/prismlogodarkmode.png
--------------------------------------------------------------------------------
/client/assets/selected_1_Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/selected_1_Dark.png
--------------------------------------------------------------------------------
/client/assets/selected_1_Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/selected_1_Light.png
--------------------------------------------------------------------------------
/client/assets/selected_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/selected_2.png
--------------------------------------------------------------------------------
/client/assets/selected_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/selected_3.png
--------------------------------------------------------------------------------
/client/assets/selected_4_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/selected_4_dark.png
--------------------------------------------------------------------------------
/client/assets/selected_4_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/selected_4_light.png
--------------------------------------------------------------------------------
/client/assets/selected_5_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/selected_5_dark.png
--------------------------------------------------------------------------------
/client/assets/selected_5_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/assets/selected_5_light.png
--------------------------------------------------------------------------------
/client/components/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, FC, useEffect } from 'react';
2 | import { Route, Routes, useNavigate } from 'react-router-dom';
3 | import Login from './Login';
4 | import Signup from './Signup';
5 | // import LandingPage from './LandingPage/LandingPage';
6 |
7 | import { ThemeProvider } from './ClusterView/ClusterViewHeader/themeContext';
8 | import Dashboard from './ClusterView/Dashboard';
9 |
10 | interface Props {}
11 |
12 | const App: FC = () => {
13 | const [user, setUser] = useState({});
14 | const [rerender, setRerender] = useState(false);
15 | const navigate = useNavigate();
16 |
17 | // effect hook will get access token if
18 | useEffect(() => {
19 | const queryString = window.location.search;
20 | const urlParams = new URLSearchParams(queryString);
21 | const codeParam = urlParams.get('code');
22 | console.log(codeParam);
23 |
24 | if (codeParam && localStorage.getItem('accessToken') === null) {
25 | async function getAccessToken() {
26 | await fetch('/user/getAccessToken?code=' + codeParam, {
27 | method: 'GET',
28 | })
29 | .then((response) => {
30 | return response.json();
31 | })
32 | .then((data) => {
33 | console.log(data);
34 |
35 | if (data.access_token) {
36 | console.log('test from app.tx, then statement');
37 | localStorage.setItem('accessToken', data.access_token);
38 | setRerender(!rerender);
39 | navigate('/dashboard');
40 | }
41 | });
42 | }
43 | getAccessToken();
44 | }
45 | }, []);
46 |
47 | return (
48 | <>
49 |
50 |
51 |
52 | } />
53 | {/* } /> */}
54 | } />
55 | } />
56 |
57 | {/* }>
58 | }>
59 | }>
60 | }>
61 | }>
62 | }
65 | >
66 |
67 | }> */}
68 | {/* } /> */}
69 | {/* } />
70 | } />
71 | } /> */}
72 | {/* */}
73 |
74 |
75 |
76 | >
77 | );
78 | };
79 |
80 | export default App;
81 |
--------------------------------------------------------------------------------
/client/components/ClusterView/ClusterViewHeader/ClusterViewHeader.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import LightDarkMode from './LighDarkMode';
3 | import Profile from './Profile';
4 | import prismlogo from '../../../assets/prismlogo.png';
5 | import prismlogodarkmode from '../../../assets/prismlogodarkmode.png';
6 |
7 | interface Props {}
8 |
9 | const ClusterViewHeader: FC = () => {
10 | return (
11 |
12 |
13 |
18 |
23 |
24 | Prism
25 |
26 |
27 |
28 |
32 |
33 | );
34 | };
35 |
36 | export default ClusterViewHeader;
37 |
--------------------------------------------------------------------------------
/client/components/ClusterView/ClusterViewHeader/LighDarkMode.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useContext } from 'react';
2 | import { ThemeContext } from './themeContext';
3 | import { MdLightMode, MdDarkMode } from 'react-icons/md';
4 |
5 | interface Props {}
6 |
7 | const LightDarkMode: FC = () => {
8 | const { theme, setTheme } = useContext(ThemeContext);
9 | return (
10 |
11 | {theme === 'dark' ? (
12 | // Light mode button
13 |
14 |
Light
15 |
setTheme(theme === 'dark' ? 'light' : 'dark')}
17 | className='flex dark:bg-transparent items-center justify-center gap-1 rounded-full py-2 px-2 scale-125 dark:text-[var(--primary)] dark:hover:text-[var(--secondary)] dark:hover:bg-[var(--primary)]'
18 | >
19 |
20 |
21 |
22 | ) : (
23 | // Dark mode button
24 |
25 |
Dark
26 |
setTheme(theme === 'dark' ? 'light' : 'dark')}
28 | className='items-center bg-transparent justify-center gap-2 text-[var(--secondary)] hover:text-[var(--primary)] hover:bg-[var(--secondary)] rounded-full py-2 px-2 scale-125'
29 | >
30 |
31 |
32 |
33 | )}
34 |
35 | );
36 | };
37 |
38 | export default LightDarkMode;
39 |
--------------------------------------------------------------------------------
/client/components/ClusterView/ClusterViewHeader/Profile.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 |
3 | interface Props {
4 |
5 | }
6 |
7 | const Profile: FC = () => {
8 | return (
9 |
10 |
OSP-2
11 |
12 |
13 | );
14 | };
15 |
16 | export default Profile;
17 |
--------------------------------------------------------------------------------
/client/components/ClusterView/ClusterViewHeader/themeContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import { ThemeProps } from 'types/types';
3 |
4 | export const getInitialTheme = (): string => {
5 | if (typeof window !== 'undefined' && window.localStorage) {
6 | const storedPrefs = window.localStorage.getItem('current-theme');
7 | if (typeof storedPrefs === 'string') {
8 | return storedPrefs;
9 | }
10 | if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
11 | return 'dark';
12 | }
13 | }
14 | return 'light';
15 | };
16 |
17 | export const ThemeContext = React.createContext(null);
18 |
19 | export const ThemeProvider = ({ initialTheme, children }: ThemeProps) => {
20 | const [theme, setTheme] = React.useState(getInitialTheme);
21 |
22 | const checkTheme = (existing: string): void => {
23 | const root = window.document.documentElement;
24 | const isDark = existing === 'dark';
25 |
26 | root.classList.remove(isDark ? 'light' : 'dark');
27 | root.classList.add(existing);
28 |
29 | localStorage.setItem('current-theme', existing);
30 | };
31 |
32 | if (initialTheme) {
33 | checkTheme(initialTheme);
34 | }
35 |
36 | React.useEffect(() => {
37 | checkTheme(theme);
38 | }, [theme]);
39 |
40 | return (
41 |
42 | {children}
43 |
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/client/components/ClusterView/Dashboard.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Outlet, NavLink } from 'react-router-dom';
3 | import ClusterViewHeader from './ClusterViewHeader/ClusterViewHeader';
4 | import SidePanel from './SidePanel/SidePanel';
5 | import OverView from './Dashboard/Overview';
6 | import Iframe from 'react-iframe';
7 | import NodesView from './Dashboard/NodesView';
8 | import PodsView from './Dashboard/PodsView';
9 | import ClusterMap from './Dashboard/ClusterMap';
10 | import { ComponentType } from 'react';
11 | import type { ReactNode } from 'react';
12 |
13 | interface SidePanelProps {
14 | setViewOverview: (value: boolean) => void;
15 | setViewNode: (value: boolean) => void;
16 | setViewPods: (value: boolean) => void;
17 | setViewClusterMap: (value: boolean) => void;
18 | }
19 |
20 | export default function Dashboard() {
21 | const [viewOverview, setViewOverview] = useState(true);
22 | const [viewNode, setViewNode] = useState(false);
23 | const [viewPods, setViewPods] = useState(false);
24 | const [viewClusterMap, setViewClusterMap] = useState(false);
25 |
26 | const [frames, updateFrames] = React.useState>([
27 | null,
28 | ]);
29 |
30 | const getURL = async (): Promise => {
31 | const urlObj = await fetch('/api', {
32 | method: 'POST',
33 | headers: {
34 | 'Content-Type': 'application/json',
35 | },
36 | }).then((response) => response.json());
37 | // return url string from object
38 | return urlObj.frameURL;
39 | };
40 |
41 | React.useEffect(() => {
42 | getURL()
43 | .then((urlString) => {
44 | const frameArray : Array = [] = [];
45 | const panelIdArray = [
46 | '1',
47 | '3',
48 | '12',
49 | '4',
50 | '27',
51 | '25',
52 | '28',
53 | '10',
54 | '13',
55 | '26',
56 | '30',
57 | '18',
58 | '17',
59 | '19',
60 | '22',
61 | '20',
62 | '21',
63 | '23',
64 | '5',
65 | '29',
66 | ]; // the panels we want to access
67 | // console.log('effect hook running: ', urlString);
68 | if (urlString) {
69 | // iterate through panel ids that we want and edit the url for each one , pushing to panels array
70 | panelIdArray.forEach((id) =>
71 | frameArray.push(
72 |
77 | )
78 | );
79 | }
80 | // state hook updates array
81 | updateFrames(frameArray);
82 | })
83 | .catch((err) => console.log(`Error in effect hook: \n ${err}`));
84 | }, []);
85 |
86 | return (
87 |
88 | {' '}
89 |
92 |
100 |
104 | {viewOverview ? : null}
105 | {viewNode ? : null}
106 | {viewPods ? : null}
107 | {viewClusterMap ? : null}
108 |
109 |
110 | );
111 | }
112 |
--------------------------------------------------------------------------------
/client/components/ClusterView/Dashboard/ClusterMap.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 |
3 | interface Props {
4 |
5 | }
6 |
7 | const ClusterMap: FC = () => {
8 | return (
9 |
10 | {' '}
11 | ClusterMap
12 |
13 | );
14 | };
15 |
16 | export default ClusterMap;
17 |
--------------------------------------------------------------------------------
/client/components/ClusterView/Dashboard/NodesView.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import type { ReactNode } from 'react';
3 |
4 | interface Props {
5 | frames: Array;
6 | }
7 |
8 | const NodesView: FC = ({ frames }) => {
9 | return {frames}
;
10 | };
11 |
12 | export default NodesView;
13 |
--------------------------------------------------------------------------------
/client/components/ClusterView/Dashboard/Overview.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useState } from 'react';
2 | //import { IIframe } from 'react-iframe/types';
3 | import { ComponentType } from 'react';
4 | import type { ReactNode } from 'react';
5 |
6 | interface OveriewProps {
7 | frames: Array;
8 | }
9 |
10 | const OverView: FC = ({ frames }) => {
11 | return {frames}
;
12 | };
13 |
14 | export default OverView;
15 |
--------------------------------------------------------------------------------
/client/components/ClusterView/Dashboard/PodsView.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import type { ReactNode } from 'react';
3 |
4 | interface PodsViewProps {
5 | frames: Array;
6 | }
7 |
8 | const PodsView: FC = ({ frames }) => {
9 | return (
10 | <>
11 | {frames}
12 | >
13 | );
14 | };
15 |
16 | export default PodsView;
17 |
--------------------------------------------------------------------------------
/client/components/ClusterView/Dashboard/mvp_dashboard.json:
--------------------------------------------------------------------------------
1 | {
2 | "__inputs": [
3 | {
4 | "name": "DS_PROMETHEUS",
5 | "label": "Prometheus",
6 | "description": "",
7 | "type": "datasource",
8 | "pluginId": "prometheus",
9 | "pluginName": "Prometheus"
10 | }
11 | ],
12 | "__elements": {},
13 | "__requires": [
14 | {
15 | "type": "panel",
16 | "id": "bargauge",
17 | "name": "Bar gauge",
18 | "version": ""
19 | },
20 | {
21 | "type": "panel",
22 | "id": "gauge",
23 | "name": "Gauge",
24 | "version": ""
25 | },
26 | {
27 | "type": "grafana",
28 | "id": "grafana",
29 | "name": "Grafana",
30 | "version": ""
31 | },
32 | {
33 | "type": "datasource",
34 | "id": "prometheus",
35 | "name": "Prometheus",
36 | "version": "1.0.0"
37 | },
38 | {
39 | "type": "panel",
40 | "id": "stat",
41 | "name": "Stat",
42 | "version": ""
43 | }
44 | ],
45 | "annotations": {
46 | "list": [
47 | {
48 | "builtIn": 1,
49 | "datasource": {
50 | "type": "grafana",
51 | "uid": "-- Grafana --"
52 | },
53 | "enable": true,
54 | "hide": true,
55 | "iconColor": "rgba(0, 211, 255, 1)",
56 | "name": "Annotations & Alerts",
57 | "type": "dashboard"
58 | }
59 | ]
60 | },
61 | "editable": true,
62 | "fiscalYearStartMonth": 0,
63 | "graphTooltip": 0,
64 | "id": null,
65 | "links": [],
66 | "liveNow": false,
67 | "panels": [
68 | {
69 | "datasource": {
70 | "type": "prometheus",
71 | "uid": null
72 | },
73 | "fieldConfig": {
74 | "defaults": {
75 | "color": {
76 | "mode": "continuous-GrYlRd"
77 | },
78 | "mappings": [],
79 | "thresholds": {
80 | "mode": "absolute",
81 | "steps": [
82 | {
83 | "color": "green",
84 | "value": null
85 | },
86 | {
87 | "color": "red",
88 | "value": 80
89 | }
90 | ]
91 | }
92 | },
93 | "overrides": []
94 | },
95 | "gridPos": {
96 | "h": 8,
97 | "w": 12,
98 | "x": 0,
99 | "y": 0
100 | },
101 | "id": 5,
102 | "options": {
103 | "displayMode": "lcd",
104 | "minVizHeight": 10,
105 | "minVizWidth": 0,
106 | "orientation": "horizontal",
107 | "reduceOptions": {
108 | "calcs": ["lastNotNull"],
109 | "fields": "",
110 | "values": false
111 | },
112 | "showUnfilled": true,
113 | "valueMode": "color"
114 | },
115 | "pluginVersion": "",
116 | "targets": [
117 | {
118 | "datasource": {
119 | "type": "prometheus",
120 | "uid": null
121 | },
122 | "editorMode": "code",
123 | "expr": "sum(rate(container_cpu_usage_seconds_total[5m])) by (pod)",
124 | "instant": false,
125 | "range": true,
126 | "refId": "A"
127 | }
128 | ],
129 | "title": "Pod CPU Usage",
130 | "transparent": true,
131 | "type": "bargauge"
132 | },
133 | {
134 | "datasource": {
135 | "type": "prometheus",
136 | "uid": null
137 | },
138 | "description": "Total amount of CPU used",
139 | "fieldConfig": {
140 | "defaults": {
141 | "color": {
142 | "mode": "thresholds"
143 | },
144 | "mappings": [],
145 | "max": 100,
146 | "min": 0,
147 | "thresholds": {
148 | "mode": "absolute",
149 | "steps": [
150 | {
151 | "color": "green",
152 | "value": null
153 | },
154 | {
155 | "color": "red",
156 | "value": 80
157 | }
158 | ]
159 | },
160 | "unit": "percent"
161 | },
162 | "overrides": []
163 | },
164 | "gridPos": {
165 | "h": 7,
166 | "w": 4,
167 | "x": 0,
168 | "y": 8
169 | },
170 | "id": 3,
171 | "options": {
172 | "orientation": "auto",
173 | "reduceOptions": {
174 | "calcs": ["lastNotNull"],
175 | "fields": "",
176 | "values": false
177 | },
178 | "showThresholdLabels": false,
179 | "showThresholdMarkers": true
180 | },
181 | "pluginVersion": "",
182 | "targets": [
183 | {
184 | "datasource": {
185 | "type": "prometheus",
186 | "uid": null
187 | },
188 | "editorMode": "code",
189 | "expr": "sum(rate(node_cpu_seconds_total{mode!=\"idle\"}[5m])) / sum(rate(node_cpu_seconds_total[5m])) * 100",
190 | "instant": false,
191 | "range": true,
192 | "refId": "A"
193 | }
194 | ],
195 | "title": "Total CPU usage",
196 | "transparent": true,
197 | "type": "gauge"
198 | },
199 | {
200 | "datasource": {
201 | "type": "prometheus",
202 | "uid": null
203 | },
204 | "fieldConfig": {
205 | "defaults": {
206 | "mappings": [],
207 | "thresholds": {
208 | "mode": "absolute",
209 | "steps": [
210 | {
211 | "color": "green",
212 | "value": null
213 | },
214 | {
215 | "color": "red",
216 | "value": 80
217 | }
218 | ]
219 | },
220 | "unit": "short"
221 | },
222 | "overrides": []
223 | },
224 | "gridPos": {
225 | "h": 8,
226 | "w": 5,
227 | "x": 4,
228 | "y": 8
229 | },
230 | "id": 4,
231 | "options": {
232 | "colorMode": "value",
233 | "graphMode": "none",
234 | "justifyMode": "auto",
235 | "orientation": "auto",
236 | "reduceOptions": {
237 | "calcs": ["lastNotNull"],
238 | "fields": "",
239 | "values": false
240 | },
241 | "textMode": "auto"
242 | },
243 | "pluginVersion": "",
244 | "targets": [
245 | {
246 | "datasource": {
247 | "type": "prometheus",
248 | "uid": null
249 | },
250 | "editorMode": "code",
251 | "expr": "count(kube_pod_info)",
252 | "instant": false,
253 | "range": true,
254 | "refId": "A"
255 | }
256 | ],
257 | "title": "Amount of Pods",
258 | "transparent": true,
259 | "type": "stat"
260 | },
261 | {
262 | "datasource": {
263 | "type": "prometheus",
264 | "uid": null
265 | },
266 | "description": "Total RAM used in node.",
267 | "fieldConfig": {
268 | "defaults": {
269 | "color": {
270 | "mode": "palette-classic"
271 | },
272 | "mappings": [],
273 | "max": 5000,
274 | "min": 0,
275 | "thresholds": {
276 | "mode": "absolute",
277 | "steps": [
278 | {
279 | "color": "green",
280 | "value": null
281 | },
282 | {
283 | "color": "red",
284 | "value": 80
285 | }
286 | ]
287 | },
288 | "unit": "decmbytes"
289 | },
290 | "overrides": []
291 | },
292 | "gridPos": {
293 | "h": 7,
294 | "w": 4,
295 | "x": 9,
296 | "y": 8
297 | },
298 | "id": 1,
299 | "options": {
300 | "orientation": "auto",
301 | "reduceOptions": {
302 | "calcs": ["lastNotNull"],
303 | "fields": "",
304 | "values": false
305 | },
306 | "showThresholdLabels": false,
307 | "showThresholdMarkers": true
308 | },
309 | "pluginVersion": "",
310 | "targets": [
311 | {
312 | "datasource": {
313 | "type": "prometheus",
314 | "uid": null
315 | },
316 | "editorMode": "code",
317 | "expr": "(sum(node_memory_MemTotal_bytes) - sum(node_memory_MemFree_bytes)) / 1024 / 1024 ",
318 | "instant": false,
319 | "range": true,
320 | "refId": "A"
321 | }
322 | ],
323 | "title": "Total Memory Usage",
324 | "transparent": true,
325 | "type": "gauge"
326 | }
327 | ],
328 | "refresh": "",
329 | "schemaVersion": 38,
330 | "style": "dark",
331 | "tags": ["templated"],
332 | "templating": {
333 | "list": []
334 | },
335 | "time": {
336 | "from": "now-6h",
337 | "to": "now"
338 | },
339 | "timepicker": {},
340 | "timezone": "browser",
341 | "title": "Test",
342 | "uid": "a4e9fb02-6a1a-4771-8e28-fd715ec071a1",
343 | "version": 7,
344 | "weekStart": ""
345 | }
346 |
--------------------------------------------------------------------------------
/client/components/ClusterView/SidePanel/Navigation.tsx:
--------------------------------------------------------------------------------
1 | import { NavLink } from 'react-router-dom';
2 | import React, { FC } from 'react';
3 | import { AiOutlineCluster } from 'react-icons/ai';
4 | import { FaCircleNodes, FaFreebsd } from 'react-icons/fa6';
5 | import { FaServer } from 'react-icons/fa';
6 |
7 | interface Navigation {
8 | setViewOverview: (arg: boolean) => void;
9 | setViewNode: (arg: boolean) => void;
10 | setViewPods: (arg: boolean) => void;
11 | setViewClusterMap: (arg: boolean) => void;
12 | }
13 |
14 | const Navigation: FC = ({
15 | setViewOverview,
16 | setViewNode,
17 | setViewPods,
18 | setViewClusterMap,
19 | }) => {
20 | return (
21 |
22 | {/* START OF ONE LINK */}
23 | {/*
27 | overview
28 | Overview
29 | */}
30 | {
32 | setViewOverview(true);
33 | setViewNode(false);
34 | setViewPods(false);
35 | setViewClusterMap(false);
36 | }}
37 | className='hover:bg-[var(--secondary)] hover:px-2 hover:py-1 my-0 hover:text-[var(--primary)] rounded flex items-center gap-1 dark:hover:text-[var(--secondary)] dark:hover:bg-[var(--primary)]'
38 | to={''}
39 | >
40 | overview Overview
41 |
42 | {/* END OF ONE NavLink */}
43 | {/* START OF ONE NavLink */}
44 | {/*
48 |
49 | Node
50 | */}
51 |
52 | {
54 | setViewOverview(false);
55 | setViewNode(true);
56 | setViewPods(false);
57 | setViewClusterMap(false);
58 | console.log('node test');
59 | }}
60 | className='hover:bg-[var(--secondary)] hover:px-2 hover:py-1 my-0 hover:text-[var(--primary)] rounded flex items-center gap-1 dark:hover:text-[var(--secondary)] dark:hover:bg-[var(--primary)]'
61 | to={''}
62 | >
63 | {' '}
64 |
65 | Nodes{' '}
66 |
67 |
68 | {/* END OF ONE NavLink */}
69 | {/* START OF ONE NavLink */}
70 | {/*
71 |
75 |
76 | Pods
77 | */}
78 |
79 | {
81 | setViewOverview(false);
82 | setViewNode(false);
83 | setViewPods(true);
84 | setViewClusterMap(false);
85 | console.log('pod test');
86 | }}
87 | className='hover:bg-[var(--secondary)] hover:px-2 hover:py-1 my-0 hover:text-[var(--primary)] rounded flex items-center gap-1 dark:hover:text-[var(--secondary)] dark:hover:bg-[var(--primary)]'
88 | to={''}
89 | >
90 | {' '}
91 | Pods{' '}
92 |
93 |
94 | {/* END OF ONE NavLink */}
95 | {/* START OF ONE NavLink */}
96 | {/*
100 |
101 | Cluster Map
102 | */}
103 |
104 | {
106 | setViewOverview(false);
107 | setViewNode(false);
108 | setViewPods(false);
109 | setViewClusterMap(true);
110 | console.log('Clustermap test');
111 | }}
112 | className='hover:bg-[var(--secondary)] hover:px-2 hover:py-1 my-0 hover:text-[var(--primary)] rounded flex items-center gap-1 dark:hover:text-[var(--secondary)] dark:hover:bg-[var(--primary)]'
113 | to={''}
114 | >
115 | {' '}
116 | Cluster Map
117 |
118 |
119 | {/* END OF ONE NavLink */}
120 |
121 | );
122 | };
123 |
124 | export default Navigation;
125 |
--------------------------------------------------------------------------------
/client/components/ClusterView/SidePanel/SidePanel.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import Navigation from './Navigation';
3 | import prismlogo from '../../../assets/prismlogo.png';
4 | import prismlogodarkmode from '../../../assets/prismlogodarkmode.png';
5 |
6 | interface LeftPanel {
7 | setViewOverview: (value: boolean) => void;
8 | setViewNode: (value: boolean) => void;
9 | setViewPods: (value: boolean) => void;
10 | setViewClusterMap: (value: boolean) => void;
11 | }
12 |
13 | const LeftPanel: FC = ({
14 | setViewOverview,
15 | setViewNode,
16 | setViewPods,
17 | setViewClusterMap,
18 | }) => {
19 | return (
20 |
21 |
22 |
27 |
32 |
33 | Prism
34 |
35 |
36 |
42 |
43 | );
44 | };
45 |
46 | export default LeftPanel;
47 |
--------------------------------------------------------------------------------
/client/components/LandingPage/LandingFooter.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PiCopyrightBold } from 'react-icons/pi';
3 |
4 | export default function LandingFooter() {
5 | const currentYear = new Date().getFullYear();
6 |
7 | return (
8 |
9 |
38 |
39 |
Copyright
40 |
41 |
{currentYear}
42 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/client/components/LandingPage/LandingHeader.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { NavLink } from 'react-router-dom';
3 | import { HiMenu } from 'react-icons/hi';
4 | import { AiOutlineClose } from 'react-icons/ai';
5 | import LinkedIn from '../../assets/In-White-96.png';
6 | import Github from '../../assets/github-mark-white.png';
7 | import Twitter from '../../assets/logo-twitter-png-40404.png';
8 | import LightDarkMode from '../ClusterView/ClusterViewHeader/LighDarkMode';
9 | import prismlogodarkmode from '../../assets/prismlogodarkmode.png';
10 |
11 | export default function LandingHeader() {
12 | let Links = [
13 | { name: 'OVERVIEW', id: 'overview' },
14 | { name: 'FEATURES', id: 'features' },
15 | { name: 'GET STARTED', id: 'getting-started' },
16 | { name: 'MEET THE TEAM', id: 'team' },
17 | ];
18 |
19 | let [isOpen, setIsOpen] = useState(false);
20 |
21 | const handleNavLinkClick = (sectionId: string) => {
22 | const element = document.getElementById(sectionId);
23 | if (element) {
24 | element.scrollIntoView({ behavior: 'smooth' });
25 | // setIsOpen(false); // Close the mobile menu after clicking a link
26 | }
27 | };
28 |
29 | return (
30 |
31 |
32 | {/* logo section */}
33 |
{
35 | // get the element with id overview. use the Links array from the top
36 | const homePage = document.getElementById(Links[0].id);
37 | // use the scrollIntoView method to scroll to the top
38 | homePage.scrollIntoView({ behavior: 'smooth' });
39 | }}
40 | className='font-bold text-2xl cursor-pointer flex items-center gap-1 flex-col '
41 | >
42 | {/* logo here */}
43 |
48 |
Prism
49 |
50 | {/* Menu icon */}
51 |
setIsOpen(!isOpen)}
53 | className=' cursor-pointer md:hidden w-9 h-9 my-auto mx-auto bg-transparent hover:text-[var(--secondary)] hover:bg-[var(--primary)] rounded-sm flex justify-center items-center '
54 | >
55 | {isOpen ? (
56 |
57 | ) : (
58 |
59 | )}
60 |
61 | {/* link items */}
62 |
67 | {Links.map((link) => (
68 | handleNavLinkClick(link.id)}
71 | className=' p-3 cursor-pointer bg-transparent hover:text-[var(--secondary)] hover:bg-[var(--primary)] rounded-sm flex justify-center items-center'
72 | >
73 | {link.name}
74 |
75 | ))}
76 |
77 | {/* Social Media */}
78 |
106 | {/* light/Dark Mode */}
107 |
108 |
109 | Dashboard
110 |
111 |
112 |
113 | );
114 | }
115 |
--------------------------------------------------------------------------------
/client/components/LandingPage/LandingMain.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { ThemeContext } from '../ClusterView/ClusterViewHeader/themeContext';
3 | import LinkedIn from '../../assets/In-Blue-96.png';
4 | import Github from '../../assets/github-mark.png';
5 | import LinkedInDark from '../../assets/In-White-96.png';
6 | import GithubDark from '../../assets/github-mark-white.png';
7 | import screenShot1Dark from '../../assets/selected_1_Dark.png';
8 | import screenShot1Light from '../../assets/selected_1_Light.png';
9 | import screenShot2Dark from '../../assets/selected_4_dark.png';
10 | import screenShot2Light from '../../assets/selected_4_light.png';
11 | import screenShot3Dark from '../../assets/selected_5_dark.png';
12 | import screenShot3Light from '../../assets/selected_5_light.png';
13 |
14 | export default function LandingMain() {
15 | const { theme, setTheme } = useContext(ThemeContext);
16 |
17 | const introText = `Welcome to Prism, your powerful Kubernetes visualizer. Effortlessly explore and monitor vital metrics for data-driven decisions. Leveraging Prometheus and Grafana, Prism delivers real-time insights, ensuring optimal performance and seamless monitoring.`;
18 |
19 | const features = [
20 | 'OAuth',
21 | 'Interactive Grafana Visualizations',
22 | 'Seamless Prometheus Integration',
23 | 'Detailed Cluster Insights',
24 | ];
25 |
26 | const meetTheTeam = [
27 | {
28 | name: 'Beserat Tafesse',
29 | photoURL:
30 | 'https://ca.slack-edge.com/T04T7FR40G1-U056W6M6VE1-e5babfb06bf4-512',
31 | linkedIn: 'https://www.linkedin.com/in/beserat-tafesse-b12a38165/',
32 | github: 'https://github.com/BeseratT',
33 | },
34 | {
35 | name: 'Josh Hall',
36 | photoURL:
37 | 'https://ca.slack-edge.com/T04T7FR40G1-U056G0R31TL-a45468e4ce49-512',
38 | linkedIn: 'https://www.linkedin.com/in/joshuarhall0/',
39 | github: 'https://github.com/joshuarhall',
40 | },
41 | {
42 | name: 'Paul Glenn',
43 | photoURL:
44 | 'https://ca.slack-edge.com/T04T7FR40G1-U053Y6VG5V1-ae16a90049bb-512',
45 | linkedIn: 'https://www.linkedin.com/in/paul-glenn-b35a2b25/',
46 | github: 'https://github.com/paglenn',
47 | },
48 | {
49 | name: 'James Li',
50 | photoURL:
51 | 'https://ca.slack-edge.com/T04T7FR40G1-U054H0TQB3M-9119e825a7b4-512',
52 | linkedIn: 'https://www.linkedin.com/in/jamesli0226/',
53 | github: 'https://github.com/Jxmes-Li',
54 | },
55 | {
56 | name: 'Dawit Merid',
57 | photoURL:
58 | 'https://ca.slack-edge.com/T04T7FR40G1-U04TA9ZPL83-b424442d574b-512',
59 | linkedIn: 'http://www.linkedin.com/in/dawit-merid-0358ab143',
60 | github: 'https://github.com/dawitmerid',
61 | },
62 | ];
63 |
64 | return (
65 |
66 | {/* Hero Section */}
67 |
71 |
72 | something
73 |
74 | {introText}
75 |
76 | {/* END OF Hero Section */}
77 | {/* Features Section */}
78 |
82 | {/* container for centering purpose */}
83 |
84 | {' '}
85 |
Features
86 | {/* */}
87 |
88 | {features.map((feature, index) => (
89 |
93 | {feature}
94 |
95 | ))}
96 |
97 | {/* screen shots */}
98 |
99 |
100 |
105 |
106 |
107 |
112 |
117 |
118 |
119 |
120 |
121 | {/* END OF Features Section */}
122 | {/* START OF GETTING STARTED */}
123 |
127 | Get Started
128 |
129 |
130 |
Check out our Github page and follow the Read Me file.
131 |
132 |
133 |
Check out our Medium Article
134 |
135 |
136 |
137 | {/* END OF GETTING STARTED */}
138 | {/* START of MEET The Team */}
139 |
143 | Meet The Team
144 | {/* one person */}
145 |
146 | {meetTheTeam.map((person, index) => (
147 |
151 |
{person.name}
152 |
153 |
158 |
159 |
179 |
180 | ))}
181 |
182 | {/* END OF one person */}
183 |
184 | {/* END OF MEET THE TEAM */}
185 |
186 | );
187 | }
188 |
--------------------------------------------------------------------------------
/client/components/LandingPage/LandingPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import LandingHeader from './LandingHeader';
3 | import LandingMain from './LandingMain';
4 | import LandingFooter from './LandingFooter';
5 |
6 | export default function LandingPage() {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/client/components/Login.tsx:
--------------------------------------------------------------------------------
1 | import React, { SyntheticEvent, useRef, useEffect, useState } from 'react';
2 | import { Link, useNavigate } from 'react-router-dom';
3 | import LightDarkMode from './ClusterView/ClusterViewHeader/LighDarkMode';
4 | import Cookies from 'js-cookie';
5 | interface Props {}
6 |
7 | export default function Login(/* {setUser} */) {
8 | const navigate = useNavigate();
9 | const username = useRef(null);
10 | const password = useRef(null);
11 | useEffect(() => {
12 | if (Cookies.get('oauthToken') || Cookies.get('userToken'))
13 | navigate('/dashboard');
14 | else console.log(document.cookie);
15 | }, []);
16 |
17 | const handleSubmit = async (event: SyntheticEvent) => {
18 | event.preventDefault();
19 | // type assertions for username and password to ensure they've been entered .
20 | if (username.current === null || password.current === null) {
21 | alert(' You must enter a username and password! ');
22 | return;
23 | }
24 | const name: string = username.current.value;
25 | const pwd: string = password.current.value;
26 | const res = await fetch('/user/login', {
27 | method: 'POST',
28 | headers: {
29 | 'Content-Type': 'application/json',
30 | },
31 | credentials: 'include',
32 | body: JSON.stringify({
33 | username: name,
34 | password: pwd,
35 | }),
36 | }).then((response) => response.json());
37 | if (res.auth) {
38 | // const user = await res.json();
39 | // setUser(user);
40 | navigate('/dashboard');
41 | } else {
42 | alert('incorrect username or password');
43 | }
44 | };
45 |
46 | const CLIENT_ID = 'a62670300c9169ebd3b3';
47 | const githubLogin = (e: SyntheticEvent) => {
48 | e.preventDefault();
49 | window.location.assign(
50 | 'https://www.github.com/login/oauth/authorize?client_id=' + CLIENT_ID
51 | );
52 | };
53 |
54 | //
55 | return (
56 |
99 | );
100 | }
101 |
--------------------------------------------------------------------------------
/client/components/Main/dashboard/clusterView.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/components/Main/dashboard/clusterView.jsx
--------------------------------------------------------------------------------
/client/components/Main/dashboard/dashboard.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/components/Main/dashboard/dashboard.jsx
--------------------------------------------------------------------------------
/client/components/Main/dashboard/nodesView.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/components/Main/dashboard/nodesView.jsx
--------------------------------------------------------------------------------
/client/components/Main/dashboard/overview.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/components/Main/dashboard/overview.jsx
--------------------------------------------------------------------------------
/client/components/Main/dashboard/podsView.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/components/Main/dashboard/podsView.jsx
--------------------------------------------------------------------------------
/client/components/Main/header/lighDarkMode.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/components/Main/header/lighDarkMode.jsx
--------------------------------------------------------------------------------
/client/components/Main/header/mainHeader.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/components/Main/header/mainHeader.jsx
--------------------------------------------------------------------------------
/client/components/Main/header/profile.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/components/Main/header/profile.jsx
--------------------------------------------------------------------------------
/client/components/Main/leftPanel.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/components/Main/leftPanel.jsx
--------------------------------------------------------------------------------
/client/components/Main/main.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/client/components/Main/main.jsx
--------------------------------------------------------------------------------
/client/components/Signup.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect, SyntheticEvent } from 'react';
2 | import { Link, useNavigate } from 'react-router-dom';
3 | import LightDarkMode from './ClusterView/ClusterViewHeader/LighDarkMode';
4 | import Cookies from 'js-cookie';
5 | interface Props {}
6 |
7 | export default function Signup(/*{setUser} */) {
8 | const navigate = useNavigate();
9 | const username = useRef(null);
10 | const password = useRef(null);
11 |
12 | useEffect(() => {
13 | if (Cookies.get('oauthToken') || Cookies.get('userToken'))
14 | navigate('/dashboard');
15 | else console.log(document.cookie);
16 | }, []);
17 |
18 | const handleSubmit = async (e: React.SyntheticEvent) => {
19 | e.preventDefault();
20 | // type assertions for username and password
21 | if (username.current === null || password.current === null) {
22 | alert(' You must enter a username and password! ');
23 | return;
24 | }
25 | const name: string = username.current.value;
26 | const pwd: string = password.current.value;
27 | const res = await fetch('/user/signup', {
28 | method: 'POST',
29 | body: JSON.stringify({
30 | username: name,
31 | password: pwd,
32 | }),
33 | headers: {
34 | 'Content-Type': 'application/json',
35 | },
36 | })
37 | .then((response) => response.json())
38 | .then((user) => {
39 | if (user.created) navigate('/dashboard');
40 | else alert('Username is taken!');
41 | });
42 | };
43 |
44 | useEffect(() => {
45 | const queryString = window.location.search;
46 | const urlParams = new URLSearchParams(queryString);
47 | // codeParam is the access token
48 | const codeParam = urlParams.get('code');
49 | console.log(codeParam);
50 | }, []);
51 |
52 | const CLIENT_ID = 'a62670300c9169ebd3b3';
53 | const githubLogin = (event: SyntheticEvent) => {
54 | event.preventDefault();
55 | window.location.assign(
56 | 'https://www.github.com/login/oauth/authorize?client_id=' + CLIENT_ID
57 | );
58 | };
59 |
60 | return (
61 |
103 | );
104 | }
105 |
--------------------------------------------------------------------------------
/client/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { HashRouter } from 'react-router-dom';
4 | import App from './components/App';
5 | import { createRoot } from 'react-dom/client';
6 |
7 | import './styles_output.css';
8 | import './styles.css';
9 |
10 | const rootElement = document.getElementById('root');
11 | const root = createRoot(rootElement);
12 |
13 | root.render(
14 |
15 |
16 |
17 |
18 |
19 | );
20 |
21 | // ReactDOM.render(
22 | //
23 | //
24 | //
25 | //
26 | // ,
27 | // document.getElementById('root')
28 | // )
29 |
--------------------------------------------------------------------------------
/client/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 | /* @import 'tailwindcss/base';
5 | @import 'tailwindcss/components';
6 | @import 'tailwindcss/utilities'; */
7 |
8 | @import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,400;0,500;0,600;0,700;1,300;1,400;1,600&display=swap');
9 |
10 | :root {
11 | --primary: #aaeec4;
12 | --primary-600: #aaeec4be;
13 | --primary-400: #c5f1d6;
14 | --secondary: #22252b;
15 | /* --primary-white: #ffffff; */
16 | --primary-white: #ffffffec;
17 | --primary-grey: #e8e8e8;
18 | }
19 |
20 | @layer base {
21 | html {
22 | @apply scroll-smooth;
23 | }
24 |
25 | body {
26 | font-family: 'Nunito', sans-serif;
27 | font-weight: 600;
28 | /* font-stretch: semi-condensed; */
29 | letter-spacing: -0.01em;
30 | }
31 |
32 | li {
33 | @apply p-4 text-sm;
34 | }
35 |
36 | button {
37 | @apply flex items-center justify-center px-2 py-1 border bg-[var(--secondary)] text-[var(--primary)] dark:bg-[var(--primary)] dark:text-[var(--secondary)] rounded border-none;
38 | }
39 |
40 | h2 {
41 | @apply text-xl font-bold;
42 | }
43 | }
44 |
45 | .material-symbols-outlined {
46 | font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 48;
47 | }
48 |
49 | .panelContainerBig {
50 | display: flex;
51 | justify-content: center;
52 | }
53 |
54 | .panelContainer {
55 | display: flex;
56 | flex-wrap: wrap;
57 | justify-content: center;
58 | padding: 1rem 1rem;
59 | gap: 2rem;
60 | width: min-content;
61 | width: 95%;
62 | height: fit-content;
63 | /* border: 2px solid yellow; */
64 | }
65 |
66 | /* overview panels */
67 | .panel1,
68 | .panel3,
69 | .panel4,
70 | .panel27,
71 | .panel3,
72 | .panel12 {
73 | width: 300px;
74 | height: auto;
75 |
76 | border-radius: 1rem;
77 | box-shadow: 5px 5px 6px rgba(0, 0, 0, 0.5);
78 | }
79 |
80 | .panel25,
81 | .panel28,
82 | .panel10,
83 | .panel13,
84 | .panel26 {
85 | width: 560px;
86 | height: 250px;
87 | border-radius: 1rem;
88 | box-shadow: 5px 5px 6px rgba(0, 0, 0, 0.5);
89 | }
90 |
91 | /* Nodes View */
92 |
93 | .panel30,
94 | .panel18,
95 | .panel17,
96 | .panel19 {
97 | width: 300px;
98 | height: auto;
99 |
100 | border-radius: 1rem;
101 | box-shadow: 5px 5px 6px rgba(0, 0, 0, 0.5);
102 | }
103 |
104 | .panel20,
105 | .panel21,
106 | .panel22 {
107 | width: 560px;
108 | height: 250px;
109 | border-radius: 1rem;
110 | box-shadow: 5px 5px 6px rgba(0, 0, 0, 0.5);
111 | }
112 | .panel23 {
113 | width: 560px;
114 | height: 550px;
115 | border-radius: 1rem;
116 | box-shadow: 5px 5px 6px rgba(0, 0, 0, 0.5);
117 | }
118 |
119 | /* Pods View */
120 | .panel5 {
121 | width: 960px;
122 | height: 300px;
123 | border-radius: 1rem;
124 | box-shadow: 5px 5px 6px rgba(0, 0, 0, 0.5);
125 | }
126 | .panel29 {
127 | width: 960px;
128 | height: 300px;
129 | border-radius: 1rem;
130 | box-shadow: 5px 5px 6px rgba(0, 0, 0, 0.5);
131 | /* width: 700px; */
132 | }
133 |
134 | @media screen and (max-width: 1024px) {
135 | .panelContainer {
136 | display: flex;
137 | flex-wrap: nowrap;
138 | align-items: center;
139 | flex-direction: column;
140 | flex: 0 0 calc(100% - 10px);
141 | border-radius: 1rem;
142 | box-shadow: 5px 5px 6px rgba(0, 0, 0, 0.5);
143 | padding: 0 3px;
144 | }
145 |
146 | .panel1,
147 | .panel3,
148 | .panel4,
149 | .panel27,
150 | .panel3,
151 | .panel12,
152 | .panel25,
153 | .panel28,
154 | .panel10,
155 | .panel13,
156 | .panel26,
157 | .panel30,
158 | .panel18,
159 | .panel17,
160 | .panel19,
161 | .panel20,
162 | .panel21,
163 | .panel22,
164 | .panel23,
165 | .panel5,
166 | .panel29 {
167 | width: 400px;
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/client/styles_output.css:
--------------------------------------------------------------------------------
1 | /*
2 | ! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com
3 | */
4 |
5 | /*
6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
8 | */
9 |
10 | *,
11 | ::before,
12 | ::after {
13 | box-sizing: border-box;
14 | /* 1 */
15 | border-width: 0;
16 | /* 2 */
17 | border-style: solid;
18 | /* 2 */
19 | border-color: #e5e7eb;
20 | /* 2 */
21 | }
22 |
23 | ::before,
24 | ::after {
25 | --tw-content: '';
26 | }
27 |
28 | /*
29 | 1. Use a consistent sensible line-height in all browsers.
30 | 2. Prevent adjustments of font size after orientation changes in iOS.
31 | 3. Use a more readable tab size.
32 | 4. Use the user's configured `sans` font-family by default.
33 | 5. Use the user's configured `sans` font-feature-settings by default.
34 | 6. Use the user's configured `sans` font-variation-settings by default.
35 | */
36 |
37 | html {
38 | line-height: 1.5;
39 | /* 1 */
40 | -webkit-text-size-adjust: 100%;
41 | /* 2 */
42 | -moz-tab-size: 4;
43 | /* 3 */
44 | -o-tab-size: 4;
45 | tab-size: 4;
46 | /* 3 */
47 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
48 | 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
49 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
50 | /* 4 */
51 | font-feature-settings: normal;
52 | /* 5 */
53 | font-variation-settings: normal;
54 | /* 6 */
55 | }
56 |
57 | /*
58 | 1. Remove the margin in all browsers.
59 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
60 | */
61 |
62 | body {
63 | margin: 0;
64 | /* 1 */
65 | line-height: inherit;
66 | /* 2 */
67 | }
68 |
69 | /*
70 | 1. Add the correct height in Firefox.
71 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
72 | 3. Ensure horizontal rules are visible by default.
73 | */
74 |
75 | hr {
76 | height: 0;
77 | /* 1 */
78 | color: inherit;
79 | /* 2 */
80 | border-top-width: 1px;
81 | /* 3 */
82 | }
83 |
84 | /*
85 | Add the correct text decoration in Chrome, Edge, and Safari.
86 | */
87 |
88 | abbr:where([title]) {
89 | -webkit-text-decoration: underline dotted;
90 | text-decoration: underline dotted;
91 | }
92 |
93 | /*
94 | Remove the default font size and weight for headings.
95 | */
96 |
97 | h1,
98 | h2,
99 | h3,
100 | h4,
101 | h5,
102 | h6 {
103 | font-size: inherit;
104 | font-weight: inherit;
105 | }
106 |
107 | /*
108 | Reset links to optimize for opt-in styling instead of opt-out.
109 | */
110 |
111 | a {
112 | color: inherit;
113 | text-decoration: inherit;
114 | }
115 |
116 | /*
117 | Add the correct font weight in Edge and Safari.
118 | */
119 |
120 | b,
121 | strong {
122 | font-weight: bolder;
123 | }
124 |
125 | /*
126 | 1. Use the user's configured `mono` font family by default.
127 | 2. Correct the odd `em` font sizing in all browsers.
128 | */
129 |
130 | code,
131 | kbd,
132 | samp,
133 | pre {
134 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
135 | 'Liberation Mono', 'Courier New', monospace;
136 | /* 1 */
137 | font-size: 1em;
138 | /* 2 */
139 | }
140 |
141 | /*
142 | Add the correct font size in all browsers.
143 | */
144 |
145 | small {
146 | font-size: 80%;
147 | }
148 |
149 | /*
150 | Prevent `sub` and `sup` elements from affecting the line height in all browsers.
151 | */
152 |
153 | sub,
154 | sup {
155 | font-size: 75%;
156 | line-height: 0;
157 | position: relative;
158 | vertical-align: baseline;
159 | }
160 |
161 | sub {
162 | bottom: -0.25em;
163 | }
164 |
165 | sup {
166 | top: -0.5em;
167 | }
168 |
169 | /*
170 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
171 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
172 | 3. Remove gaps between table borders by default.
173 | */
174 |
175 | table {
176 | text-indent: 0;
177 | /* 1 */
178 | border-color: inherit;
179 | /* 2 */
180 | border-collapse: collapse;
181 | /* 3 */
182 | }
183 |
184 | /*
185 | 1. Change the font styles in all browsers.
186 | 2. Remove the margin in Firefox and Safari.
187 | 3. Remove default padding in all browsers.
188 | */
189 |
190 | button,
191 | input,
192 | optgroup,
193 | select,
194 | textarea {
195 | font-family: inherit;
196 | /* 1 */
197 | font-feature-settings: inherit;
198 | /* 1 */
199 | font-variation-settings: inherit;
200 | /* 1 */
201 | font-size: 100%;
202 | /* 1 */
203 | font-weight: inherit;
204 | /* 1 */
205 | line-height: inherit;
206 | /* 1 */
207 | color: inherit;
208 | /* 1 */
209 | margin: 0;
210 | /* 2 */
211 | padding: 0;
212 | /* 3 */
213 | }
214 |
215 | /*
216 | Remove the inheritance of text transform in Edge and Firefox.
217 | */
218 |
219 | button,
220 | select {
221 | text-transform: none;
222 | }
223 |
224 | /*
225 | 1. Correct the inability to style clickable types in iOS and Safari.
226 | 2. Remove default button styles.
227 | */
228 |
229 | button,
230 | [type='button'],
231 | [type='reset'],
232 | [type='submit'] {
233 | -webkit-appearance: button;
234 | /* 1 */
235 | background-color: transparent;
236 | /* 2 */
237 | background-image: none;
238 | /* 2 */
239 | }
240 |
241 | /*
242 | Use the modern Firefox focus style for all focusable elements.
243 | */
244 |
245 | :-moz-focusring {
246 | outline: auto;
247 | }
248 |
249 | /*
250 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
251 | */
252 |
253 | :-moz-ui-invalid {
254 | box-shadow: none;
255 | }
256 |
257 | /*
258 | Add the correct vertical alignment in Chrome and Firefox.
259 | */
260 |
261 | progress {
262 | vertical-align: baseline;
263 | }
264 |
265 | /*
266 | Correct the cursor style of increment and decrement buttons in Safari.
267 | */
268 |
269 | ::-webkit-inner-spin-button,
270 | ::-webkit-outer-spin-button {
271 | height: auto;
272 | }
273 |
274 | /*
275 | 1. Correct the odd appearance in Chrome and Safari.
276 | 2. Correct the outline style in Safari.
277 | */
278 |
279 | [type='search'] {
280 | -webkit-appearance: textfield;
281 | /* 1 */
282 | outline-offset: -2px;
283 | /* 2 */
284 | }
285 |
286 | /*
287 | Remove the inner padding in Chrome and Safari on macOS.
288 | */
289 |
290 | ::-webkit-search-decoration {
291 | -webkit-appearance: none;
292 | }
293 |
294 | /*
295 | 1. Correct the inability to style clickable types in iOS and Safari.
296 | 2. Change font properties to `inherit` in Safari.
297 | */
298 |
299 | ::-webkit-file-upload-button {
300 | -webkit-appearance: button;
301 | /* 1 */
302 | font: inherit;
303 | /* 2 */
304 | }
305 |
306 | /*
307 | Add the correct display in Chrome and Safari.
308 | */
309 |
310 | summary {
311 | display: list-item;
312 | }
313 |
314 | /*
315 | Removes the default spacing and border for appropriate elements.
316 | */
317 |
318 | blockquote,
319 | dl,
320 | dd,
321 | h1,
322 | h2,
323 | h3,
324 | h4,
325 | h5,
326 | h6,
327 | hr,
328 | figure,
329 | p,
330 | pre {
331 | margin: 0;
332 | }
333 |
334 | fieldset {
335 | margin: 0;
336 | padding: 0;
337 | }
338 |
339 | legend {
340 | padding: 0;
341 | }
342 |
343 | ol,
344 | ul,
345 | menu {
346 | list-style: none;
347 | margin: 0;
348 | padding: 0;
349 | }
350 |
351 | /*
352 | Reset default styling for dialogs.
353 | */
354 |
355 | dialog {
356 | padding: 0;
357 | }
358 |
359 | /*
360 | Prevent resizing textareas horizontally by default.
361 | */
362 |
363 | textarea {
364 | resize: vertical;
365 | }
366 |
367 | /*
368 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
369 | 2. Set the default placeholder color to the user's configured gray 400 color.
370 | */
371 |
372 | input::-moz-placeholder,
373 | textarea::-moz-placeholder {
374 | opacity: 1;
375 | /* 1 */
376 | color: #9ca3af;
377 | /* 2 */
378 | }
379 |
380 | input::placeholder,
381 | textarea::placeholder {
382 | opacity: 1;
383 | /* 1 */
384 | color: #9ca3af;
385 | /* 2 */
386 | }
387 |
388 | /*
389 | Set the default cursor for buttons.
390 | */
391 |
392 | button,
393 | [role='button'] {
394 | cursor: pointer;
395 | }
396 |
397 | /*
398 | Make sure disabled buttons don't get the pointer cursor.
399 | */
400 |
401 | :disabled {
402 | cursor: default;
403 | }
404 |
405 | /*
406 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
407 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
408 | This can trigger a poorly considered lint error in some tools but is included by design.
409 | */
410 |
411 | img,
412 | svg,
413 | video,
414 | canvas,
415 | audio,
416 | iframe,
417 | embed,
418 | object {
419 | display: block;
420 | /* 1 */
421 | vertical-align: middle;
422 | /* 2 */
423 | }
424 |
425 | /*
426 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
427 | */
428 |
429 | img,
430 | video {
431 | max-width: 100%;
432 | height: auto;
433 | }
434 |
435 | .overviewpanels {
436 | max-width: 30%;
437 | margin: 5px;
438 | }
439 |
440 | /* Make elements with the HTML hidden attribute stay hidden by default */
441 |
442 | [hidden] {
443 | display: none;
444 | }
445 |
446 | html {
447 | scroll-behavior: smooth;
448 | }
449 |
450 | body {
451 | font-family: 'Nunito', sans-serif;
452 | font-weight: 600;
453 | /* font-stretch: semi-condensed; */
454 | letter-spacing: -0.06em;
455 | }
456 |
457 | li {
458 | padding: 1rem;
459 | font-size: 0.875rem;
460 | line-height: 1.25rem;
461 | }
462 |
463 | button {
464 | display: flex;
465 | align-items: center;
466 | justify-content: center;
467 | border-radius: 0.25rem;
468 | border-width: 1px;
469 | border-style: none;
470 | background-color: var(--secondary);
471 | padding-left: 0.5rem;
472 | padding-right: 0.5rem;
473 | padding-top: 0.25rem;
474 | padding-bottom: 0.25rem;
475 | color: var(--primary);
476 | }
477 |
478 | :is(.dark button) {
479 | background-color: var(--primary);
480 | color: var(--secondary);
481 | }
482 |
483 | h2 {
484 | font-size: 1.25rem;
485 | line-height: 1.75rem;
486 | font-weight: 700;
487 | }
488 |
489 | *,
490 | ::before,
491 | ::after {
492 | --tw-border-spacing-x: 0;
493 | --tw-border-spacing-y: 0;
494 | --tw-translate-x: 0;
495 | --tw-translate-y: 0;
496 | --tw-rotate: 0;
497 | --tw-skew-x: 0;
498 | --tw-skew-y: 0;
499 | --tw-scale-x: 1;
500 | --tw-scale-y: 1;
501 | --tw-pan-x: ;
502 | --tw-pan-y: ;
503 | --tw-pinch-zoom: ;
504 | --tw-scroll-snap-strictness: proximity;
505 | --tw-gradient-from-position: ;
506 | --tw-gradient-via-position: ;
507 | --tw-gradient-to-position: ;
508 | --tw-ordinal: ;
509 | --tw-slashed-zero: ;
510 | --tw-numeric-figure: ;
511 | --tw-numeric-spacing: ;
512 | --tw-numeric-fraction: ;
513 | --tw-ring-inset: ;
514 | --tw-ring-offset-width: 0px;
515 | --tw-ring-offset-color: #fff;
516 | --tw-ring-color: rgb(59 130 246 / 0.5);
517 | --tw-ring-offset-shadow: 0 0 #0000;
518 | --tw-ring-shadow: 0 0 #0000;
519 | --tw-shadow: 0 0 #0000;
520 | --tw-shadow-colored: 0 0 #0000;
521 | --tw-blur: ;
522 | --tw-brightness: ;
523 | --tw-contrast: ;
524 | --tw-grayscale: ;
525 | --tw-hue-rotate: ;
526 | --tw-invert: ;
527 | --tw-saturate: ;
528 | --tw-sepia: ;
529 | --tw-drop-shadow: ;
530 | --tw-backdrop-blur: ;
531 | --tw-backdrop-brightness: ;
532 | --tw-backdrop-contrast: ;
533 | --tw-backdrop-grayscale: ;
534 | --tw-backdrop-hue-rotate: ;
535 | --tw-backdrop-invert: ;
536 | --tw-backdrop-opacity: ;
537 | --tw-backdrop-saturate: ;
538 | --tw-backdrop-sepia: ;
539 | }
540 |
541 | ::backdrop {
542 | --tw-border-spacing-x: 0;
543 | --tw-border-spacing-y: 0;
544 | --tw-translate-x: 0;
545 | --tw-translate-y: 0;
546 | --tw-rotate: 0;
547 | --tw-skew-x: 0;
548 | --tw-skew-y: 0;
549 | --tw-scale-x: 1;
550 | --tw-scale-y: 1;
551 | --tw-pan-x: ;
552 | --tw-pan-y: ;
553 | --tw-pinch-zoom: ;
554 | --tw-scroll-snap-strictness: proximity;
555 | --tw-gradient-from-position: ;
556 | --tw-gradient-via-position: ;
557 | --tw-gradient-to-position: ;
558 | --tw-ordinal: ;
559 | --tw-slashed-zero: ;
560 | --tw-numeric-figure: ;
561 | --tw-numeric-spacing: ;
562 | --tw-numeric-fraction: ;
563 | --tw-ring-inset: ;
564 | --tw-ring-offset-width: 0px;
565 | --tw-ring-offset-color: #fff;
566 | --tw-ring-color: rgb(59 130 246 / 0.5);
567 | --tw-ring-offset-shadow: 0 0 #0000;
568 | --tw-ring-shadow: 0 0 #0000;
569 | --tw-shadow: 0 0 #0000;
570 | --tw-shadow-colored: 0 0 #0000;
571 | --tw-blur: ;
572 | --tw-brightness: ;
573 | --tw-contrast: ;
574 | --tw-grayscale: ;
575 | --tw-hue-rotate: ;
576 | --tw-invert: ;
577 | --tw-saturate: ;
578 | --tw-sepia: ;
579 | --tw-drop-shadow: ;
580 | --tw-backdrop-blur: ;
581 | --tw-backdrop-brightness: ;
582 | --tw-backdrop-contrast: ;
583 | --tw-backdrop-grayscale: ;
584 | --tw-backdrop-hue-rotate: ;
585 | --tw-backdrop-invert: ;
586 | --tw-backdrop-opacity: ;
587 | --tw-backdrop-saturate: ;
588 | --tw-backdrop-sepia: ;
589 | }
590 |
591 | .my-0 {
592 | margin-top: 0px;
593 | margin-bottom: 0px;
594 | }
595 |
596 | .mb-0 {
597 | margin-bottom: 0px;
598 | }
599 |
600 | .mb-1 {
601 | margin-bottom: 0.25rem;
602 | }
603 |
604 | .flex {
605 | display: flex;
606 | }
607 |
608 | .grid {
609 | display: grid;
610 | }
611 |
612 | .hidden {
613 | display: none;
614 | }
615 |
616 | .h-12 {
617 | height: 3rem;
618 | }
619 |
620 | .h-\[10rem\] {
621 | height: 10rem;
622 | }
623 |
624 | .h-full {
625 | height: 100%;
626 | }
627 |
628 | .min-h-screen {
629 | min-height: 100vh;
630 | }
631 |
632 | .w-12 {
633 | width: 3rem;
634 | }
635 |
636 | .w-\[2\.8rem\] {
637 | width: 2.8rem;
638 | }
639 |
640 | .scale-125 {
641 | --tw-scale-x: 1.25;
642 | --tw-scale-y: 1.25;
643 | transform: translate(var(--tw-translate-x), var(--tw-translate-y))
644 | rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
645 | scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
646 | }
647 |
648 | .flex-col {
649 | flex-direction: column;
650 | }
651 |
652 | .items-center {
653 | align-items: center;
654 | }
655 |
656 | .items-stretch {
657 | align-items: stretch;
658 | }
659 |
660 | .justify-center {
661 | justify-content: center;
662 | }
663 |
664 | .justify-between {
665 | justify-content: space-between;
666 | }
667 |
668 | .gap-1 {
669 | gap: 0.25rem;
670 | }
671 |
672 | .gap-2 {
673 | gap: 0.5rem;
674 | }
675 |
676 | .gap-3 {
677 | gap: 0.75rem;
678 | }
679 |
680 | .gap-5 {
681 | gap: 1.25rem;
682 | }
683 |
684 | .rounded {
685 | border-radius: 0.25rem;
686 | }
687 |
688 | .rounded-full {
689 | border-radius: 9999px;
690 | }
691 |
692 | .border-b-\[3px\] {
693 | border-bottom-width: 3px;
694 | }
695 |
696 | .border-\[var\(--primary-grey\)\] {
697 | border-color: var(--primary-grey);
698 | }
699 |
700 | .bg-\[var\(--primary-grey\)\] {
701 | background-color: var(--primary-grey);
702 | }
703 |
704 | .bg-\[var\(--primary-white\)\] {
705 | background-color: var(--primary-white);
706 | }
707 |
708 | .bg-\[var\(--secondary\)\] {
709 | background-color: var(--secondary);
710 | }
711 |
712 | .bg-transparent {
713 | background-color: transparent;
714 | }
715 |
716 | .px-2 {
717 | padding-left: 0.5rem;
718 | padding-right: 0.5rem;
719 | }
720 |
721 | .px-6 {
722 | padding-left: 1.5rem;
723 | padding-right: 1.5rem;
724 | }
725 |
726 | .py-2 {
727 | padding-top: 0.5rem;
728 | padding-bottom: 0.5rem;
729 | }
730 |
731 | .py-7 {
732 | padding-top: 1.75rem;
733 | padding-bottom: 1.75rem;
734 | }
735 |
736 | .text-\[2\.3rem\] {
737 | font-size: 2.3rem;
738 | }
739 |
740 | .font-bold {
741 | font-weight: 700;
742 | }
743 |
744 | .text-\[var\(--secondary\)\] {
745 | color: var(--secondary);
746 | }
747 |
748 | .drop-shadow-2xl {
749 | --tw-drop-shadow: drop-shadow(6px 5px 5px rgba(0, 0, 0, 0.2));
750 | filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast)
751 | var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate)
752 | var(--tw-sepia) var(--tw-drop-shadow);
753 | }
754 |
755 | /* @import 'tailwindcss/base';
756 | @import 'tailwindcss/components';
757 | @import 'tailwindcss/utilities'; */
758 |
759 | :root {
760 | --primary: #aaeec4;
761 | --primary-dark: #aaeec4ad;
762 | --secondary: #22252b;
763 | --primary-white: #ffffff;
764 | --primary-grey: #e8e8e8;
765 | }
766 |
767 | .material-symbols-outlined {
768 | font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 48;
769 | }
770 |
771 | .hover\:bg-\[var\(--secondary\)\]:hover {
772 | background-color: var(--secondary);
773 | }
774 |
775 | .hover\:px-2:hover {
776 | padding-left: 0.5rem;
777 | padding-right: 0.5rem;
778 | }
779 |
780 | .hover\:py-1:hover {
781 | padding-top: 0.25rem;
782 | padding-bottom: 0.25rem;
783 | }
784 |
785 | .hover\:text-\[var\(--primary\)\]:hover {
786 | color: var(--primary);
787 | }
788 |
789 | :is(.dark .dark\:block) {
790 | display: block;
791 | }
792 |
793 | :is(.dark .dark\:hidden) {
794 | display: none;
795 | }
796 |
797 | :is(.dark .dark\:border-\[var\(--primary\)\]) {
798 | border-color: var(--primary);
799 | }
800 |
801 | :is(.dark .dark\:bg-\[var\(--primary\)\]) {
802 | background-color: var(--primary);
803 | }
804 |
805 | :is(.dark .dark\:bg-\[var\(--secondary\)\]) {
806 | background-color: var(--secondary);
807 | }
808 |
809 | :is(.dark .dark\:bg-transparent) {
810 | background-color: transparent;
811 | }
812 |
813 | :is(.dark .dark\:text-\[var\(--primary\)\]) {
814 | color: var(--primary);
815 | }
816 |
817 | :is(.dark .dark\:drop-shadow-3xl) {
818 | --tw-drop-shadow: drop-shadow(6px 5px 5px rgba(0, 0, 0, 0.5));
819 | filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast)
820 | var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate)
821 | var(--tw-sepia) var(--tw-drop-shadow);
822 | }
823 |
824 | :is(.dark .dark\:hover\:bg-\[var\(--primary\)\]:hover) {
825 | background-color: var(--primary);
826 | }
827 |
828 | :is(.dark .dark\:hover\:text-\[var\(--secondary\)\]:hover) {
829 | color: var(--secondary);
830 | }
831 |
832 | @media (min-width: 640px) {
833 | .sm\:block {
834 | display: block;
835 | }
836 |
837 | .sm\:flex {
838 | display: flex;
839 | }
840 |
841 | .sm\:grid {
842 | display: grid;
843 | }
844 |
845 | .sm\:hidden {
846 | display: none;
847 | }
848 |
849 | .sm\:h-full {
850 | height: 100%;
851 | }
852 |
853 | .sm\:auto-rows-max {
854 | grid-auto-rows: max-content;
855 | }
856 |
857 | .sm\:grid-cols-layout {
858 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
859 | }
860 |
861 | .sm\:grid-rows-layout {
862 | grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
863 | }
864 |
865 | .sm\:flex-col {
866 | flex-direction: column;
867 | }
868 |
869 | .sm\:justify-end {
870 | justify-content: flex-end;
871 | }
872 |
873 | .sm\:px-10 {
874 | padding-left: 2.5rem;
875 | padding-right: 2.5rem;
876 | }
877 |
878 | .sm\:grid-areas-layout {
879 | grid-template-areas:
880 | 'sidebar header header header header header'
881 | 'sidebar main main main main main '
882 | 'sidebar main main main main main'
883 | 'sidebar main main main main main'
884 | 'sidebar main main main main main'
885 | 'sidebar main main main main main'
886 | 'sidebar main main main main main'
887 | 'sidebar main main main main main'
888 | 'sidebar main main main main main'
889 | 'sidebar main main main main main';
890 | }
891 |
892 | .sm\:grid-in-sidebar {
893 | grid-area: sidebar;
894 | }
895 |
896 | .sm\:grid-in-header {
897 | grid-area: header;
898 | }
899 |
900 | .sm\:grid-in-main {
901 | grid-area: main;
902 | }
903 | }
904 |
--------------------------------------------------------------------------------
/coverage/coverage-summary.json:
--------------------------------------------------------------------------------
1 | {"total": {"lines":{"total":290,"covered":9,"skipped":0,"pct":3.1},"statements":{"total":294,"covered":9,"skipped":0,"pct":3.06},"functions":{"total":81,"covered":2,"skipped":0,"pct":2.46},"branches":{"total":74,"covered":0,"skipped":0,"pct":0},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}}
2 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/index.tsx": {"lines":{"total":3,"covered":0,"skipped":0,"pct":0},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":3,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
3 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/App.tsx": {"lines":{"total":18,"covered":0,"skipped":0,"pct":0},"functions":{"total":5,"covered":0,"skipped":0,"pct":0},"statements":{"total":18,"covered":0,"skipped":0,"pct":0},"branches":{"total":6,"covered":0,"skipped":0,"pct":0}}
4 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/Login.tsx": {"lines":{"total":21,"covered":9,"skipped":0,"pct":42.85},"functions":{"total":8,"covered":2,"skipped":0,"pct":25},"statements":{"total":21,"covered":9,"skipped":0,"pct":42.85},"branches":{"total":2,"covered":0,"skipped":0,"pct":0}}
5 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/Signup.tsx": {"lines":{"total":19,"covered":0,"skipped":0,"pct":0},"functions":{"total":7,"covered":0,"skipped":0,"pct":0},"statements":{"total":19,"covered":0,"skipped":0,"pct":0},"branches":{"total":2,"covered":0,"skipped":0,"pct":0}}
6 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/ClusterView/Dashboard.tsx": {"lines":{"total":19,"covered":0,"skipped":0,"pct":0},"functions":{"total":7,"covered":0,"skipped":0,"pct":0},"statements":{"total":19,"covered":0,"skipped":0,"pct":0},"branches":{"total":10,"covered":0,"skipped":0,"pct":0}}
7 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/ClusterView/ClusterViewHeader/ClusterViewHeader.tsx": {"lines":{"total":2,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":2,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
8 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/ClusterView/ClusterViewHeader/LighDarkMode.tsx": {"lines":{"total":5,"covered":0,"skipped":0,"pct":0},"functions":{"total":3,"covered":0,"skipped":0,"pct":0},"statements":{"total":5,"covered":0,"skipped":0,"pct":0},"branches":{"total":6,"covered":0,"skipped":0,"pct":0}}
9 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/ClusterView/ClusterViewHeader/Profile.tsx": {"lines":{"total":2,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":2,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
10 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/ClusterView/ClusterViewHeader/themeContext.tsx": {"lines":{"total":22,"covered":0,"skipped":0,"pct":0},"functions":{"total":4,"covered":0,"skipped":0,"pct":0},"statements":{"total":22,"covered":0,"skipped":0,"pct":0},"branches":{"total":12,"covered":0,"skipped":0,"pct":0}}
11 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/ClusterView/Dashboard/ClusterMap.tsx": {"lines":{"total":2,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":2,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
12 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/ClusterView/Dashboard/NodesView.tsx": {"lines":{"total":2,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":2,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
13 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/ClusterView/Dashboard/Overview.tsx": {"lines":{"total":2,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":2,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
14 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/ClusterView/Dashboard/PodsView.tsx": {"lines":{"total":2,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":2,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
15 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/ClusterView/SidePanel/Navigation.tsx": {"lines":{"total":21,"covered":0,"skipped":0,"pct":0},"functions":{"total":5,"covered":0,"skipped":0,"pct":0},"statements":{"total":21,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
16 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/ClusterView/SidePanel/SidePanel.tsx": {"lines":{"total":2,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":2,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
17 | ,"/Users/nls.pglenn/github/Codesmith/Prism/client/components/LandingPage/LandingPage.tsx": {"lines":{"total":1,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":1,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
18 | ,"/Users/nls.pglenn/github/Codesmith/Prism/server/server.ts": {"lines":{"total":19,"covered":0,"skipped":0,"pct":0},"functions":{"total":4,"covered":0,"skipped":0,"pct":0},"statements":{"total":19,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
19 | ,"/Users/nls.pglenn/github/Codesmith/Prism/server/controllers/metricsController.ts": {"lines":{"total":26,"covered":0,"skipped":0,"pct":0},"functions":{"total":8,"covered":0,"skipped":0,"pct":0},"statements":{"total":28,"covered":0,"skipped":0,"pct":0},"branches":{"total":10,"covered":0,"skipped":0,"pct":0}}
20 | ,"/Users/nls.pglenn/github/Codesmith/Prism/server/controllers/userController.ts": {"lines":{"total":55,"covered":0,"skipped":0,"pct":0},"functions":{"total":6,"covered":0,"skipped":0,"pct":0},"statements":{"total":56,"covered":0,"skipped":0,"pct":0},"branches":{"total":22,"covered":0,"skipped":0,"pct":0}}
21 | ,"/Users/nls.pglenn/github/Codesmith/Prism/server/db/db.ts": {"lines":{"total":6,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":6,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
22 | ,"/Users/nls.pglenn/github/Codesmith/Prism/server/db/models/userSchema.ts": {"lines":{"total":11,"covered":0,"skipped":0,"pct":0},"functions":{"total":2,"covered":0,"skipped":0,"pct":0},"statements":{"total":12,"covered":0,"skipped":0,"pct":0},"branches":{"total":2,"covered":0,"skipped":0,"pct":0}}
23 | ,"/Users/nls.pglenn/github/Codesmith/Prism/server/routers/apiRouter.ts": {"lines":{"total":8,"covered":0,"skipped":0,"pct":0},"functions":{"total":3,"covered":0,"skipped":0,"pct":0},"statements":{"total":8,"covered":0,"skipped":0,"pct":0},"branches":{"total":2,"covered":0,"skipped":0,"pct":0}}
24 | ,"/Users/nls.pglenn/github/Codesmith/Prism/server/routers/userRouter.ts": {"lines":{"total":22,"covered":0,"skipped":0,"pct":0},"functions":{"total":10,"covered":0,"skipped":0,"pct":0},"statements":{"total":22,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
25 | ,"/Users/nls.pglenn/github/Codesmith/Prism/types/index.d.ts": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
26 | ,"/Users/nls.pglenn/github/Codesmith/Prism/types/types.ts": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
27 | }
28 |
--------------------------------------------------------------------------------
/custom-test-env.js:
--------------------------------------------------------------------------------
1 | const Environment = require('jest-environment-jsdom');
2 |
3 | /**
4 | * A custom environment to set the TextEncoder that is required by TensorFlow.js.
5 | */
6 | module.exports = class CustomTestEnvironment extends Environment {
7 | async setup() {
8 | await super.setup();
9 | if (typeof this.global.TextEncoder === 'undefined') {
10 | const { TextEncoder } = require('util');
11 | this.global.TextEncoder = TextEncoder;
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/grafana/api_token.json:
--------------------------------------------------------------------------------
1 | {"id":1,"name":"apikeycurl0","key":"eyJrIjoiN28zS2hwZndUS3NBQ2oxNG1iempyaG9Kam41RTREbUgiLCJuIjoiYXBpa2V5Y3VybDAiLCJpZCI6MX0="}
--------------------------------------------------------------------------------
/grafana/config_values.yaml:
--------------------------------------------------------------------------------
1 | global:
2 | # To help compatibility with other charts which use global.imagePullSecrets.
3 | # Allow either an array of {name: pullSecret} maps (k8s-style), or an array of strings (more common helm-style).
4 | # Can be tempalted.
5 | # global:
6 | # imagePullSecrets:
7 | # - name: pullSecret1
8 | # - name: pullSecret2
9 | # or
10 | # global:
11 | # imagePullSecrets:
12 | # - pullSecret1
13 | # - pullSecret2
14 | imagePullSecrets: []
15 |
16 | rbac:
17 | create: true
18 | ## Use an existing ClusterRole/Role (depending on rbac.namespaced false/true)
19 | # useExistingRole: name-of-some-(cluster)role
20 | pspEnabled: false
21 | pspUseAppArmor: false
22 | namespaced: false
23 | extraRoleRules: []
24 | # - apiGroups: []
25 | # resources: []
26 | # verbs: []
27 | extraClusterRoleRules: []
28 | # - apiGroups: []
29 | # resources: []
30 | # verbs: []
31 | serviceAccount:
32 | create: true
33 | name:
34 | nameTest:
35 | ## ServiceAccount labels.
36 | labels: {}
37 | ## Service account annotations. Can be templated.
38 | # annotations:
39 | # eks.amazonaws.com/role-arn: arn:aws:iam::123456789000:role/iam-role-name-here
40 | autoMount: true
41 |
42 | replicas: 1
43 |
44 | ## Create a headless service for the deployment
45 | headlessService: false
46 |
47 | ## Create HorizontalPodAutoscaler object for deployment type
48 | #
49 | autoscaling:
50 | enabled: false
51 | minReplicas: 1
52 | maxReplicas: 5
53 | targetCPU: "60"
54 | targetMemory: ""
55 | behavior: {}
56 |
57 | ## See `kubectl explain poddisruptionbudget.spec` for more
58 | ## ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/
59 | podDisruptionBudget: {}
60 | # minAvailable: 1
61 | # maxUnavailable: 1
62 |
63 | ## See `kubectl explain deployment.spec.strategy` for more
64 | ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy
65 | deploymentStrategy:
66 | type: RollingUpdate
67 |
68 | readinessProbe:
69 | httpGet:
70 | path: /api/health
71 | port: 3000
72 |
73 | livenessProbe:
74 | httpGet:
75 | path: /api/health
76 | port: 3000
77 | initialDelaySeconds: 60
78 | timeoutSeconds: 30
79 | failureThreshold: 10
80 |
81 | ## Use an alternate scheduler, e.g. "stork".
82 | ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/
83 | ##
84 | # schedulerName: "default-scheduler"
85 |
86 | image:
87 | repository: docker.io/grafana/grafana
88 | # Overrides the Grafana image tag whose default is the chart appVersion
89 | tag: ""
90 | sha: ""
91 | pullPolicy: IfNotPresent
92 |
93 | ## Optionally specify an array of imagePullSecrets.
94 | ## Secrets must be manually created in the namespace.
95 | ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
96 | ## Can be templated.
97 | ##
98 | pullSecrets: []
99 | # - myRegistrKeySecretName
100 |
101 | testFramework:
102 | enabled: true
103 | image: docker.io/bats/bats
104 | tag: "v1.4.1"
105 | imagePullPolicy: IfNotPresent
106 | securityContext: {}
107 |
108 | securityContext:
109 | runAsNonRoot: true
110 | runAsUser: 472
111 | runAsGroup: 472
112 | fsGroup: 472
113 |
114 | containerSecurityContext:
115 | allowPrivilegeEscalation: false
116 | capabilities:
117 | drop:
118 | - ALL
119 | seccompProfile:
120 | type: RuntimeDefault
121 |
122 | # Enable creating the grafana configmap
123 | createConfigmap: true
124 |
125 | # Extra configmaps to mount in grafana pods
126 | # Values are templated.
127 | extraConfigmapMounts:
128 | []
129 | # - name: certs-configmap
130 | # mountPath: /etc/grafana/ssl/
131 | # subPath: certificates.crt # (optional)
132 | # configMap: certs-configmap
133 | # readOnly: true
134 |
135 | extraEmptyDirMounts:
136 | []
137 | # - name: provisioning-notifiers
138 | # mountPath: /etc/grafana/provisioning/notifiers
139 |
140 | # Apply extra labels to common labels.
141 | extraLabels: {}
142 |
143 | ## Assign a PriorityClassName to pods if set
144 | # priorityClassName:
145 |
146 | downloadDashboardsImage:
147 | repository: docker.io/curlimages/curl
148 | tag: 7.85.0
149 | sha: ""
150 | pullPolicy: IfNotPresent
151 |
152 | downloadDashboards:
153 | env: {}
154 | envFromSecret: ""
155 | resources: {}
156 | securityContext:
157 | allowPrivilegeEscalation: false
158 | capabilities:
159 | drop:
160 | - ALL
161 | seccompProfile:
162 | type: RuntimeDefault
163 | envValueFrom: {}
164 | # ENV_NAME:
165 | # configMapKeyRef:
166 | # name: configmap-name
167 | # key: value_key
168 |
169 | ## Pod Annotations
170 | # podAnnotations: {}
171 |
172 | ## Pod Labels
173 | # podLabels: {}
174 |
175 | podPortName: grafana
176 | gossipPortName: gossip
177 | ## Deployment annotations
178 | # annotations: {}
179 |
180 | ## Expose the grafana service to be accessed from outside the cluster (LoadBalancer service).
181 | ## or access it from within the cluster (ClusterIP service). Set the service type and the port to serve it.
182 | ## ref: http://kubernetes.io/docs/user-guide/services/
183 | ##
184 | service:
185 | enabled: true
186 | type: ClusterIP
187 | port: 80
188 | targetPort:
189 | 3000
190 | # targetPort: 4181 To be used with a proxy extraContainer
191 | ## Service annotations. Can be templated.
192 | annotations: {}
193 | labels: {}
194 | portName: service
195 | # Adds the appProtocol field to the service. This allows to work with istio protocol selection. Ex: "http" or "tcp"
196 | appProtocol: ""
197 |
198 | serviceMonitor:
199 | ## If true, a ServiceMonitor CRD is created for a prometheus operator
200 | ## https://github.com/coreos/prometheus-operator
201 | ##
202 | enabled: false
203 | path: /metrics
204 | # namespace: monitoring (defaults to use the namespace this chart is deployed to)
205 | labels: {}
206 | interval: 1m
207 | scheme: http
208 | tlsConfig: {}
209 | scrapeTimeout: 30s
210 | relabelings: []
211 | targetLabels: []
212 |
213 | extraExposePorts:
214 | []
215 | # - name: keycloak
216 | # port: 8080
217 | # targetPort: 8080
218 | # type: ClusterIP
219 |
220 | # overrides pod.spec.hostAliases in the grafana deployment's pods
221 | hostAliases:
222 | []
223 | # - ip: "1.2.3.4"
224 | # hostnames:
225 | # - "my.host.com"
226 |
227 | ingress:
228 | enabled: false
229 | # For Kubernetes >= 1.18 you should specify the ingress-controller via the field ingressClassName
230 | # See https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/#specifying-the-class-of-an-ingress
231 | # ingressClassName: nginx
232 | # Values can be templated
233 | annotations:
234 | {}
235 | # kubernetes.io/ingress.class: nginx
236 | # kubernetes.io/tls-acme: "true"
237 | labels: {}
238 | path: /
239 |
240 | # pathType is only for k8s >= 1.1=
241 | pathType: Prefix
242 |
243 | hosts:
244 | - chart-example.local
245 | ## Extra paths to prepend to every host configuration. This is useful when working with annotation based services.
246 | extraPaths: []
247 | # - path: /*
248 | # backend:
249 | # serviceName: ssl-redirect
250 | # servicePort: use-annotation
251 | ## Or for k8s > 1.19
252 | # - path: /*
253 | # pathType: Prefix
254 | # backend:
255 | # service:
256 | # name: ssl-redirect
257 | # port:
258 | # name: use-annotation
259 |
260 | tls: []
261 | # - secretName: chart-example-tls
262 | # hosts:
263 | # - chart-example.local
264 |
265 | resources: {}
266 | # limits:
267 | # cpu: 100m
268 | # memory: 128Mi
269 | # requests:
270 | # cpu: 100m
271 | # memory: 128Mi
272 |
273 | ## Node labels for pod assignment
274 | ## ref: https://kubernetes.io/docs/user-guide/node-selection/
275 | #
276 | nodeSelector: {}
277 |
278 | ## Tolerations for pod assignment
279 | ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
280 | ##
281 | tolerations: []
282 |
283 | ## Affinity for pod assignment (evaluated as template)
284 | ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
285 | ##
286 | affinity: {}
287 |
288 | ## Topology Spread Constraints
289 | ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
290 | ##
291 | topologySpreadConstraints: []
292 |
293 | ## Additional init containers (evaluated as template)
294 | ## ref: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
295 | ##
296 | extraInitContainers: []
297 |
298 | ## Enable an Specify container in extraContainers. This is meant to allow adding an authentication proxy to a grafana pod
299 | extraContainers: ""
300 | # extraContainers: |
301 | # - name: proxy
302 | # image: quay.io/gambol99/keycloak-proxy:latest
303 | # args:
304 | # - -provider=github
305 | # - -client-id=
306 | # - -client-secret=
307 | # - -github-org=
308 | # - -email-domain=*
309 | # - -cookie-secret=
310 | # - -http-address=http://0.0.0.0:4181
311 | # - -upstream-url=http://127.0.0.1:3000
312 | # ports:
313 | # - name: proxy-web
314 | # containerPort: 4181
315 |
316 | ## Volumes that can be used in init containers that will not be mounted to deployment pods
317 | extraContainerVolumes: []
318 | # - name: volume-from-secret
319 | # secret:
320 | # secretName: secret-to-mount
321 | # - name: empty-dir-volume
322 | # emptyDir: {}
323 |
324 | ## Enable persistence using Persistent Volume Claims
325 | ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/
326 | ##
327 | persistence:
328 | type: pvc
329 | enabled: false
330 | # storageClassName: default
331 | accessModes:
332 | - ReadWriteOnce
333 | size: 10Gi
334 | # annotations: {}
335 | finalizers:
336 | - kubernetes.io/pvc-protection
337 | # selectorLabels: {}
338 | ## Sub-directory of the PV to mount. Can be templated.
339 | # subPath: ""
340 | ## Name of an existing PVC. Can be templated.
341 | # existingClaim:
342 | ## Extra labels to apply to a PVC.
343 | extraPvcLabels: {}
344 |
345 | ## If persistence is not enabled, this allows to mount the
346 | ## local storage in-memory to improve performance
347 | ##
348 | inMemory:
349 | enabled: false
350 | ## The maximum usage on memory medium EmptyDir would be
351 | ## the minimum value between the SizeLimit specified
352 | ## here and the sum of memory limits of all containers in a pod
353 | ##
354 | # sizeLimit: 300Mi
355 |
356 | initChownData:
357 | ## If false, data ownership will not be reset at startup
358 | ## This allows the grafana-server to be run with an arbitrary user
359 | ##
360 | enabled: true
361 |
362 | ## initChownData container image
363 | ##
364 | image:
365 | repository: docker.io/library/busybox
366 | tag: "1.31.1"
367 | sha: ""
368 | pullPolicy: IfNotPresent
369 |
370 | ## initChownData resource requests and limits
371 | ## Ref: http://kubernetes.io/docs/user-guide/compute-resources/
372 | ##
373 | resources: {}
374 | # limits:
375 | # cpu: 100m
376 | # memory: 128Mi
377 | # requests:
378 | # cpu: 100m
379 | # memory: 128Mi
380 | securityContext:
381 | runAsNonRoot: false
382 | runAsUser: 0
383 | seccompProfile:
384 | type: RuntimeDefault
385 | capabilities:
386 | add:
387 | - CHOWN
388 |
389 | # Administrator credentials when not using an existing secret (see below)
390 | adminUser: admin
391 | # adminPassword: strongpassword
392 |
393 | # Use an existing secret for the admin user.
394 | admin:
395 | ## Name of the secret. Can be templated.
396 | existingSecret: ""
397 | userKey: admin-user
398 | passwordKey: admin-password
399 |
400 | ## Define command to be executed at startup by grafana container
401 | ## Needed if using `vault-env` to manage secrets (ref: https://banzaicloud.com/blog/inject-secrets-into-pods-vault/)
402 | ## Default is "run.sh" as defined in grafana's Dockerfile
403 | # command:
404 | # - "sh"
405 | # - "/run.sh"
406 |
407 | ## Optionally define args if command is used
408 | ## Needed if using `hashicorp/envconsul` to manage secrets
409 | ## By default no arguments are set
410 | # args:
411 | # - "-secret"
412 | # - "secret/grafana"
413 | # - "./grafana"
414 |
415 | ## Extra environment variables that will be pass onto deployment pods
416 | ##
417 | ## to provide grafana with access to CloudWatch on AWS EKS:
418 | ## 1. create an iam role of type "Web identity" with provider oidc.eks.* (note the provider for later)
419 | ## 2. edit the "Trust relationships" of the role, add a line inside the StringEquals clause using the
420 | ## same oidc eks provider as noted before (same as the existing line)
421 | ## also, replace NAMESPACE and prometheus-operator-grafana with the service account namespace and name
422 | ##
423 | ## "oidc.eks.us-east-1.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:sub": "system:serviceaccount:NAMESPACE:prometheus-operator-grafana",
424 | ##
425 | ## 3. attach a policy to the role, you can use a built in policy called CloudWatchReadOnlyAccess
426 | ## 4. use the following env: (replace 123456789000 and iam-role-name-here with your aws account number and role name)
427 | ##
428 | ## env:
429 | ## AWS_ROLE_ARN: arn:aws:iam::123456789000:role/iam-role-name-here
430 | ## AWS_WEB_IDENTITY_TOKEN_FILE: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
431 | ## AWS_REGION: us-east-1
432 | ##
433 | ## 5. uncomment the EKS section in extraSecretMounts: below
434 | ## 6. uncomment the annotation section in the serviceAccount: above
435 | ## make sure to replace arn:aws:iam::123456789000:role/iam-role-name-here with your role arn
436 |
437 | env: {}
438 |
439 | ## "valueFrom" environment variable references that will be added to deployment pods. Name is templated.
440 | ## ref: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvarsource-v1-core
441 | ## Renders in container spec as:
442 | ## env:
443 | ## ...
444 | ## - name:
445 | ## valueFrom:
446 | ##
447 | envValueFrom:
448 | {}
449 | # ENV_NAME:
450 | # configMapKeyRef:
451 | # name: configmap-name
452 | # key: value_key
453 |
454 | ## The name of a secret in the same kubernetes namespace which contain values to be added to the environment
455 | ## This can be useful for auth tokens, etc. Value is templated.
456 | envFromSecret: ""
457 |
458 | ## Sensible environment variables that will be rendered as new secret object
459 | ## This can be useful for auth tokens, etc.
460 | ## If the secret values contains "{{", they'll need to be properly escaped so that they are not interpreted by Helm
461 | ## ref: https://helm.sh/docs/howto/charts_tips_and_tricks/#using-the-tpl-function
462 | envRenderSecret: {}
463 |
464 | ## The names of secrets in the same kubernetes namespace which contain values to be added to the environment
465 | ## Each entry should contain a name key, and can optionally specify whether the secret must be defined with an optional key.
466 | ## Name is templated.
467 | envFromSecrets: []
468 | ## - name: secret-name
469 | ## optional: true
470 |
471 | ## The names of conifgmaps in the same kubernetes namespace which contain values to be added to the environment
472 | ## Each entry should contain a name key, and can optionally specify whether the configmap must be defined with an optional key.
473 | ## Name is templated.
474 | ## ref: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#configmapenvsource-v1-core
475 | envFromConfigMaps: []
476 | ## - name: configmap-name
477 | ## optional: true
478 |
479 | # Inject Kubernetes services as environment variables.
480 | # See https://kubernetes.io/docs/concepts/services-networking/connect-applications-service/#environment-variables
481 | enableServiceLinks: true
482 |
483 | ## Additional grafana server secret mounts
484 | # Defines additional mounts with secrets. Secrets must be manually created in the namespace.
485 | extraSecretMounts:
486 | []
487 | # - name: secret-files
488 | # mountPath: /etc/secrets
489 | # secretName: grafana-secret-files
490 | # readOnly: true
491 | # subPath: ""
492 | #
493 | # for AWS EKS (cloudwatch) use the following (see also instruction in env: above)
494 | # - name: aws-iam-token
495 | # mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount
496 | # readOnly: true
497 | # projected:
498 | # defaultMode: 420
499 | # sources:
500 | # - serviceAccountToken:
501 | # audience: sts.amazonaws.com
502 | # expirationSeconds: 86400
503 | # path: token
504 | #
505 | # for CSI e.g. Azure Key Vault use the following
506 | # - name: secrets-store-inline
507 | # mountPath: /run/secrets
508 | # readOnly: true
509 | # csi:
510 | # driver: secrets-store.csi.k8s.io
511 | # readOnly: true
512 | # volumeAttributes:
513 | # secretProviderClass: "akv-grafana-spc"
514 | # nodePublishSecretRef: # Only required when using service principal mode
515 | # name: grafana-akv-creds # Only required when using service principal mode
516 |
517 | ## Additional grafana server volume mounts
518 | # Defines additional volume mounts.
519 | extraVolumeMounts:
520 | []
521 | # - name: extra-volume-0
522 | # mountPath: /mnt/volume0
523 | # readOnly: true
524 | # existingClaim: volume-claim
525 | # - name: extra-volume-1
526 | # mountPath: /mnt/volume1
527 | # readOnly: true
528 | # hostPath: /usr/shared/
529 | # - name: grafana-secrets
530 | # csi: true
531 | # data:
532 | # driver: secrets-store.csi.k8s.io
533 | # readOnly: true
534 | # volumeAttributes:
535 | # secretProviderClass: "grafana-env-spc"
536 |
537 | ## Container Lifecycle Hooks. Execute a specific bash command or make an HTTP request
538 | lifecycleHooks:
539 | {}
540 | # postStart:
541 | # exec:
542 | # command: []
543 |
544 | ## Pass the plugins you want installed as a list.
545 | ##
546 | plugins:
547 | []
548 | # - digrich-bubblechart-panel
549 | # - grafana-clock-panel
550 | ## You can also use other plugin download URL, as long as they are valid zip files,
551 | ## and specify the name of the plugin after the semicolon. Like this:
552 | # - https://grafana.com/api/plugins/marcusolsson-json-datasource/versions/1.3.2/download;marcusolsson-json-datasource
553 |
554 | ## Configure grafana datasources
555 | ## ref: http://docs.grafana.org/administration/provisioning/#datasources
556 | ##
557 | datasources:
558 | datasources.yaml:
559 | apiVersion: 1
560 | datasources:
561 | - name: Prometheus
562 | type: prometheus
563 | url: http://prometheus-server:80
564 | access: proxy
565 | isDefault: true
566 |
567 | ## Configure grafana alerting (can be templated)
568 | ## ref: http://docs.grafana.org/administration/provisioning/#alerting
569 | ##
570 | alerting:
571 | {}
572 | # rules.yaml:
573 | # apiVersion: 1
574 | # groups:
575 | # - orgId: 1
576 | # name: '{{ .Chart.Name }}_my_rule_group'
577 | # folder: my_first_folder
578 | # interval: 60s
579 | # rules:
580 | # - uid: my_id_1
581 | # title: my_first_rule
582 | # condition: A
583 | # data:
584 | # - refId: A
585 | # datasourceUid: '-100'
586 | # model:
587 | # conditions:
588 | # - evaluator:
589 | # params:
590 | # - 3
591 | # type: gt
592 | # operator:
593 | # type: and
594 | # query:
595 | # params:
596 | # - A
597 | # reducer:
598 | # type: last
599 | # type: query
600 | # datasource:
601 | # type: __expr__
602 | # uid: '-100'
603 | # expression: 1==0
604 | # intervalMs: 1000
605 | # maxDataPoints: 43200
606 | # refId: A
607 | # type: math
608 | # dashboardUid: my_dashboard
609 | # panelId: 123
610 | # noDataState: Alerting
611 | # for: 60s
612 | # annotations:
613 | # some_key: some_value
614 | # labels:
615 | # team: sre_team_1
616 | # contactpoints.yaml:
617 | # apiVersion: 1
618 | # contactPoints:
619 | # - orgId: 1
620 | # name: cp_1
621 | # receivers:
622 | # - uid: first_uid
623 | # type: pagerduty
624 | # settings:
625 | # integrationKey: XXX
626 | # severity: critical
627 | # class: ping failure
628 | # component: Grafana
629 | # group: app-stack
630 | # summary: |
631 | # {{ `{{ include "default.message" . }}` }}
632 |
633 | ## Configure notifiers
634 | ## ref: http://docs.grafana.org/administration/provisioning/#alert-notification-channels
635 | ##
636 | notifiers: {}
637 | # notifiers.yaml:
638 | # notifiers:
639 | # - name: email-notifier
640 | # type: email
641 | # uid: email1
642 | # # either:
643 | # org_id: 1
644 | # # or
645 | # org_name: Main Org.
646 | # is_default: true
647 | # settings:
648 | # addresses: an_email_address@example.com
649 | # delete_notifiers:
650 |
651 | ## Configure grafana dashboard providers
652 | ## ref: http://docs.grafana.org/administration/provisioning/#dashboards
653 | ##
654 | ## `path` must be /var/lib/grafana/dashboards/
655 | ##
656 | dashboardProviders:
657 | dashboardproviders.yaml:
658 | apiVersion: 1
659 | providers:
660 | - name: "default"
661 | orgId: 1
662 | type: file
663 | updateIntervalSecond: 10
664 | allowUiUpdates: true
665 | editable: true
666 | options:
667 | path: /var/lib/grafana/dashboards/default
668 |
669 | ## Configure grafana dashboard to import
670 | ## NOTE: To use dashboards you must also enable/configure dashboardProviders
671 | ## ref: https://grafana.com/dashboards
672 | ##
673 | ## dashboards per provider, use provider name as key.
674 | ##
675 | # dashboards:
676 | # default:
677 | # custom-dashboard:
678 | # file: 13332_rev12.json
679 |
680 | # default:
681 | # some-dashboard:
682 | # json: |
683 | # $RAW_JSON
684 | # custom-dashboard:
685 | # file: dashboards/custom-dashboard.json
686 | # prometheus-stats:
687 | # gnetId: 2
688 | # revision: 2
689 | # datasource: Prometheus
690 | # local-dashboard:
691 | # url: https://example.com/repository/test.json
692 | # token: ''
693 | # local-dashboard-base64:
694 | # url: https://example.com/repository/test-b64.json
695 | # token: ''
696 | # b64content: true
697 | # local-dashboard-gitlab:
698 | # url: https://example.com/repository/test-gitlab.json
699 | # gitlabToken: ''
700 | # local-dashboard-bitbucket:
701 | # url: https://example.com/repository/test-bitbucket.json
702 | # bearerToken: ''
703 | # local-dashboard-azure:
704 | # url: https://example.com/repository/test-azure.json
705 | # basic: ''
706 | # acceptHeader: '*/*'
707 |
708 | ## Reference to external ConfigMap per provider. Use provider name as key and ConfigMap name as value.
709 | ## A provider dashboards must be defined either by external ConfigMaps or in values.yaml, not in both.
710 | ## ConfigMap data example:
711 | ##
712 | ## data:
713 | ## example-dashboard.json: |
714 | ## RAW_JSON
715 | ##
716 | dashboardsConfigMaps: {}
717 | # default: ""
718 |
719 | ## Grafana's primary configuration
720 | ## NOTE: values in map will be converted to ini format
721 | ## ref: http://docs.grafana.org/installation/configuration/
722 | ##
723 | grafana.ini:
724 | paths:
725 | data: /var/lib/grafana/
726 | logs: /var/log/grafana
727 | plugins: /var/lib/grafana/plugins
728 | provisioning: /etc/grafana/provisioning
729 | analytics:
730 | check_for_updates: true
731 | log:
732 | mode: console
733 | grafana_net:
734 | url: https://grafana.net
735 | server:
736 | domain: "{{ if (and .Values.ingress.enabled .Values.ingress.hosts) }}{{ .Values.ingress.hosts | first }}{{ else }}''{{ end }}"
737 | http_port: 3000
738 | auth.anonymous:
739 | enabled: true
740 | org_name: Main Org.
741 | org_role: Admin
742 | hide_version: true
743 | users:
744 | auto_assign_org_role: true
745 | security:
746 | allow_embedding: true
747 |
748 | ## grafana Authentication can be enabled with the following values on grafana.ini
749 | # server:
750 | # The full public facing url you use in browser, used for redirects and emails
751 | # root_url:
752 | # https://grafana.com/docs/grafana/latest/auth/github/#enable-github-in-grafana
753 | # auth.github:
754 | # enabled: false
755 | # allow_sign_up: false
756 | # scopes: user:email,read:org
757 | # auth_url: https://github.com/login/oauth/authorize
758 | # token_url: https://github.com/login/oauth/access_token
759 | # api_url: https://api.github.com/user
760 | # team_ids:
761 | # allowed_organizations:
762 | # client_id:
763 | # client_secret:
764 | ## LDAP Authentication can be enabled with the following values on grafana.ini
765 | ## NOTE: Grafana will fail to start if the value for ldap.toml is invalid
766 | # auth.ldap:
767 | # enabled: true
768 | # allow_sign_up: true
769 | # config_file: /etc/grafana/ldap.toml
770 |
771 | ## Grafana's LDAP configuration
772 | ## Templated by the template in _helpers.tpl
773 | ## NOTE: To enable the grafana.ini must be configured with auth.ldap.enabled
774 | ## ref: http://docs.grafana.org/installation/configuration/#auth-ldap
775 | ## ref: http://docs.grafana.org/installation/ldap/#configuration
776 | ldap:
777 | enabled: false
778 | # `existingSecret` is a reference to an existing secret containing the ldap configuration
779 | # for Grafana in a key `ldap-toml`.
780 | existingSecret: ""
781 | # `config` is the content of `ldap.toml` that will be stored in the created secret
782 | config: ""
783 | # config: |-
784 | # verbose_logging = true
785 |
786 | # [[servers]]
787 | # host = "my-ldap-server"
788 | # port = 636
789 | # use_ssl = true
790 | # start_tls = false
791 | # ssl_skip_verify = false
792 | # bind_dn = "uid=%s,ou=users,dc=myorg,dc=com"
793 |
794 | ## Grafana's SMTP configuration
795 | ## NOTE: To enable, grafana.ini must be configured with smtp.enabled
796 | ## ref: http://docs.grafana.org/installation/configuration/#smtp
797 | smtp:
798 | # `existingSecret` is a reference to an existing secret containing the smtp configuration
799 | # for Grafana.
800 | existingSecret: ""
801 | userKey: "user"
802 | passwordKey: "password"
803 |
804 | ## Sidecars that collect the configmaps with specified label and stores the included files them into the respective folders
805 | ## Requires at least Grafana 5 to work and can't be used together with parameters dashboardProviders, datasources and dashboards
806 | sidecar:
807 | image:
808 | repository: quay.io/kiwigrid/k8s-sidecar
809 | tag: 1.24.6
810 | sha: ""
811 | imagePullPolicy: IfNotPresent
812 | resources: {}
813 | # limits:
814 | # cpu: 100m
815 | # memory: 100Mi
816 | # requests:
817 | # cpu: 50m
818 | # memory: 50Mi
819 | securityContext:
820 | allowPrivilegeEscalation: false
821 | capabilities:
822 | drop:
823 | - ALL
824 | seccompProfile:
825 | type: RuntimeDefault
826 | # skipTlsVerify Set to true to skip tls verification for kube api calls
827 | # skipTlsVerify: true
828 | enableUniqueFilenames: false
829 | readinessProbe: {}
830 | livenessProbe: {}
831 | # Log level default for all sidecars. Can be one of: DEBUG, INFO, WARN, ERROR, CRITICAL. Defaults to INFO
832 | # logLevel: INFO
833 | alerts:
834 | enabled: false
835 | # Additional environment variables for the alerts sidecar
836 | env: {}
837 | # Do not reprocess already processed unchanged resources on k8s API reconnect.
838 | # ignoreAlreadyProcessed: true
839 | # label that the configmaps with alert are marked with
840 | label: grafana_alert
841 | # value of label that the configmaps with alert are set to
842 | labelValue: ""
843 | # Log level. Can be one of: DEBUG, INFO, WARN, ERROR, CRITICAL.
844 | # logLevel: INFO
845 | # If specified, the sidecar will search for alert config-maps inside this namespace.
846 | # Otherwise the namespace in which the sidecar is running will be used.
847 | # It's also possible to specify ALL to search in all namespaces
848 | searchNamespace: null
849 | # Method to use to detect ConfigMap changes. With WATCH the sidecar will do a WATCH requests, with SLEEP it will list all ConfigMaps, then sleep for 60 seconds.
850 | watchMethod: WATCH
851 | # search in configmap, secret or both
852 | resource: both
853 | # watchServerTimeout: request to the server, asking it to cleanly close the connection after that.
854 | # defaults to 60sec; much higher values like 3600 seconds (1h) are feasible for non-Azure K8S
855 | # watchServerTimeout: 3600
856 | #
857 | # watchClientTimeout: is a client-side timeout, configuring your local socket.
858 | # If you have a network outage dropping all packets with no RST/FIN,
859 | # this is how long your client waits before realizing & dropping the connection.
860 | # defaults to 66sec (sic!)
861 | # watchClientTimeout: 60
862 | #
863 | # Endpoint to send request to reload alerts
864 | reloadURL: "http://localhost:3000/api/admin/provisioning/alerting/reload"
865 | # Absolute path to shell script to execute after a alert got reloaded
866 | script: null
867 | skipReload: false
868 | # Deploy the alert sidecar as an initContainer in addition to a container.
869 | # Additional alert sidecar volume mounts
870 | extraMounts: []
871 | # Sets the size limit of the alert sidecar emptyDir volume
872 | sizeLimit: {}
873 | dashboards:
874 | enabled: false
875 | # Additional environment variables for the dashboards sidecar
876 | env: {}
877 | # Do not reprocess already processed unchanged resources on k8s API reconnect.
878 | # ignoreAlreadyProcessed: true
879 | SCProvider: true
880 | # label that the configmaps with dashboards are marked with
881 | label: grafana_dashboard
882 | # value of label that the configmaps with dashboards are set to
883 | labelValue: ""
884 | # Log level. Can be one of: DEBUG, INFO, WARN, ERROR, CRITICAL.
885 | # logLevel: INFO
886 | # folder in the pod that should hold the collected dashboards (unless `defaultFolderName` is set)
887 | folder: /tmp/dashboards
888 | # The default folder name, it will create a subfolder under the `folder` and put dashboards in there instead
889 | defaultFolderName: null
890 | # Namespaces list. If specified, the sidecar will search for config-maps/secrets inside these namespaces.
891 | # Otherwise the namespace in which the sidecar is running will be used.
892 | # It's also possible to specify ALL to search in all namespaces.
893 | searchNamespace: null
894 | # Method to use to detect ConfigMap changes. With WATCH the sidecar will do a WATCH requests, with SLEEP it will list all ConfigMaps, then sleep for 60 seconds.
895 | watchMethod: WATCH
896 | # search in configmap, secret or both
897 | resource: both
898 | # If specified, the sidecar will look for annotation with this name to create folder and put graph here.
899 | # You can use this parameter together with `provider.foldersFromFilesStructure`to annotate configmaps and create folder structure.
900 | folderAnnotation: null
901 | # Endpoint to send request to reload alerts
902 | reloadURL: "http://localhost:3000/api/admin/provisioning/dashboards/reload"
903 | # Absolute path to shell script to execute after a configmap got reloaded
904 | script: null
905 | skipReload: false
906 | # watchServerTimeout: request to the server, asking it to cleanly close the connection after that.
907 | # defaults to 60sec; much higher values like 3600 seconds (1h) are feasible for non-Azure K8S
908 | # watchServerTimeout: 3600
909 | #
910 | # watchClientTimeout: is a client-side timeout, configuring your local socket.
911 | # If you have a network outage dropping all packets with no RST/FIN,
912 | # this is how long your client waits before realizing & dropping the connection.
913 | # defaults to 66sec (sic!)
914 | # watchClientTimeout: 60
915 | #
916 | # provider configuration that lets grafana manage the dashboards
917 | provider:
918 | # name of the provider, should be unique
919 | name: sidecarProvider
920 | # orgid as configured in grafana
921 | orgid: 1
922 | # folder in which the dashboards should be imported in grafana
923 | folder: ""
924 | # type of the provider
925 | type: file
926 | # disableDelete to activate a import-only behaviour
927 | disableDelete: false
928 | # allow updating provisioned dashboards from the UI
929 | allowUiUpdates: false
930 | # allow Grafana to replicate dashboard structure from filesystem
931 | foldersFromFilesStructure: false
932 | # Additional dashboard sidecar volume mounts
933 | extraMounts: []
934 | # Sets the size limit of the dashboard sidecar emptyDir volume
935 | sizeLimit: {}
936 | datasources:
937 | enabled: false
938 | # Additional environment variables for the datasourcessidecar
939 | env: {}
940 | # Do not reprocess already processed unchanged resources on k8s API reconnect.
941 | # ignoreAlreadyProcessed: true
942 | # label that the configmaps with datasources are marked with
943 | label: grafana_datasource
944 | # value of label that the configmaps with datasources are set to
945 | labelValue: ""
946 | # Log level. Can be one of: DEBUG, INFO, WARN, ERROR, CRITICAL.
947 | # logLevel: INFO
948 | # If specified, the sidecar will search for datasource config-maps inside this namespace.
949 | # Otherwise the namespace in which the sidecar is running will be used.
950 | # It's also possible to specify ALL to search in all namespaces
951 | searchNamespace: null
952 | # Method to use to detect ConfigMap changes. With WATCH the sidecar will do a WATCH requests, with SLEEP it will list all ConfigMaps, then sleep for 60 seconds.
953 | watchMethod: WATCH
954 | # search in configmap, secret or both
955 | resource: both
956 | # watchServerTimeout: request to the server, asking it to cleanly close the connection after that.
957 | # defaults to 60sec; much higher values like 3600 seconds (1h) are feasible for non-Azure K8S
958 | # watchServerTimeout: 3600
959 | #
960 | # watchClientTimeout: is a client-side timeout, configuring your local socket.
961 | # If you have a network outage dropping all packets with no RST/FIN,
962 | # this is how long your client waits before realizing & dropping the connection.
963 | # defaults to 66sec (sic!)
964 | # watchClientTimeout: 60
965 | #
966 | # Endpoint to send request to reload datasources
967 | reloadURL: "http://localhost:3000/api/admin/provisioning/datasources/reload"
968 | # Absolute path to shell script to execute after a datasource got reloaded
969 | script: null
970 | skipReload: false
971 | # Deploy the datasource sidecar as an initContainer in addition to a container.
972 | # This is needed if skipReload is true, to load any datasources defined at startup time.
973 | initDatasources: false
974 | # Sets the size limit of the datasource sidecar emptyDir volume
975 | sizeLimit: {}
976 | plugins:
977 | enabled: false
978 | # Additional environment variables for the plugins sidecar
979 | env: {}
980 | # Do not reprocess already processed unchanged resources on k8s API reconnect.
981 | # ignoreAlreadyProcessed: true
982 | # label that the configmaps with plugins are marked with
983 | label: grafana_plugin
984 | # value of label that the configmaps with plugins are set to
985 | labelValue: ""
986 | # Log level. Can be one of: DEBUG, INFO, WARN, ERROR, CRITICAL.
987 | # logLevel: INFO
988 | # If specified, the sidecar will search for plugin config-maps inside this namespace.
989 | # Otherwise the namespace in which the sidecar is running will be used.
990 | # It's also possible to specify ALL to search in all namespaces
991 | searchNamespace: null
992 | # Method to use to detect ConfigMap changes. With WATCH the sidecar will do a WATCH requests, with SLEEP it will list all ConfigMaps, then sleep for 60 seconds.
993 | watchMethod: WATCH
994 | # search in configmap, secret or both
995 | resource: both
996 | # watchServerTimeout: request to the server, asking it to cleanly close the connection after that.
997 | # defaults to 60sec; much higher values like 3600 seconds (1h) are feasible for non-Azure K8S
998 | # watchServerTimeout: 3600
999 | #
1000 | # watchClientTimeout: is a client-side timeout, configuring your local socket.
1001 | # If you have a network outage dropping all packets with no RST/FIN,
1002 | # this is how long your client waits before realizing & dropping the connection.
1003 | # defaults to 66sec (sic!)
1004 | # watchClientTimeout: 60
1005 | #
1006 | # Endpoint to send request to reload plugins
1007 | reloadURL: "http://localhost:3000/api/admin/provisioning/plugins/reload"
1008 | # Absolute path to shell script to execute after a plugin got reloaded
1009 | script: null
1010 | skipReload: false
1011 | # Deploy the datasource sidecar as an initContainer in addition to a container.
1012 | # This is needed if skipReload is true, to load any plugins defined at startup time.
1013 | initPlugins: false
1014 | # Sets the size limit of the plugin sidecar emptyDir volume
1015 | sizeLimit: {}
1016 | notifiers:
1017 | enabled: false
1018 | # Additional environment variables for the notifierssidecar
1019 | env: {}
1020 | # Do not reprocess already processed unchanged resources on k8s API reconnect.
1021 | # ignoreAlreadyProcessed: true
1022 | # label that the configmaps with notifiers are marked with
1023 | label: grafana_notifier
1024 | # value of label that the configmaps with notifiers are set to
1025 | labelValue: ""
1026 | # Log level. Can be one of: DEBUG, INFO, WARN, ERROR, CRITICAL.
1027 | # logLevel: INFO
1028 | # If specified, the sidecar will search for notifier config-maps inside this namespace.
1029 | # Otherwise the namespace in which the sidecar is running will be used.
1030 | # It's also possible to specify ALL to search in all namespaces
1031 | searchNamespace: null
1032 | # Method to use to detect ConfigMap changes. With WATCH the sidecar will do a WATCH requests, with SLEEP it will list all ConfigMaps, then sleep for 60 seconds.
1033 | watchMethod: WATCH
1034 | # search in configmap, secret or both
1035 | resource: both
1036 | # watchServerTimeout: request to the server, asking it to cleanly close the connection after that.
1037 | # defaults to 60sec; much higher values like 3600 seconds (1h) are feasible for non-Azure K8S
1038 | # watchServerTimeout: 3600
1039 | #
1040 | # watchClientTimeout: is a client-side timeout, configuring your local socket.
1041 | # If you have a network outage dropping all packets with no RST/FIN,
1042 | # this is how long your client waits before realizing & dropping the connection.
1043 | # defaults to 66sec (sic!)
1044 | # watchClientTimeout: 60
1045 | #
1046 | # Endpoint to send request to reload notifiers
1047 | reloadURL: "http://localhost:3000/api/admin/provisioning/notifications/reload"
1048 | # Absolute path to shell script to execute after a notifier got reloaded
1049 | script: null
1050 | skipReload: false
1051 | # Deploy the notifier sidecar as an initContainer in addition to a container.
1052 | # This is needed if skipReload is true, to load any notifiers defined at startup time.
1053 | initNotifiers: false
1054 | # Sets the size limit of the notifier sidecar emptyDir volume
1055 | sizeLimit: {}
1056 |
1057 | ## Override the deployment namespace
1058 | ##
1059 | namespaceOverride: ""
1060 |
1061 | ## Number of old ReplicaSets to retain
1062 | ##
1063 | revisionHistoryLimit: 10
1064 |
1065 | ## Add a seperate remote image renderer deployment/service
1066 | imageRenderer:
1067 | deploymentStrategy: {}
1068 | # Enable the image-renderer deployment & service
1069 | enabled: false
1070 | replicas: 1
1071 | autoscaling:
1072 | enabled: false
1073 | minReplicas: 1
1074 | maxReplicas: 5
1075 | targetCPU: "60"
1076 | targetMemory: ""
1077 | behavior: {}
1078 | image:
1079 | # image-renderer Image repository
1080 | repository: docker.io/grafana/grafana-image-renderer
1081 | # image-renderer Image tag
1082 | tag: latest
1083 | # image-renderer Image sha (optional)
1084 | sha: ""
1085 | # image-renderer ImagePullPolicy
1086 | pullPolicy: Always
1087 | # extra environment variables
1088 | env:
1089 | HTTP_HOST: "0.0.0.0"
1090 | # RENDERING_ARGS: --no-sandbox,--disable-gpu,--window-size=1280x758
1091 | # RENDERING_MODE: clustered
1092 | # IGNORE_HTTPS_ERRORS: true
1093 |
1094 | ## "valueFrom" environment variable references that will be added to deployment pods. Name is templated.
1095 | ## ref: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvarsource-v1-core
1096 | ## Renders in container spec as:
1097 | ## env:
1098 | ## ...
1099 | ## - name:
1100 | ## valueFrom:
1101 | ##
1102 | envValueFrom:
1103 | {}
1104 | # ENV_NAME:
1105 | # configMapKeyRef:
1106 | # name: configmap-name
1107 | # key: value_key
1108 |
1109 | # image-renderer deployment serviceAccount
1110 | serviceAccountName: ""
1111 | # image-renderer deployment securityContext
1112 | securityContext: {}
1113 | # image-renderer deployment container securityContext
1114 | containerSecurityContext:
1115 | seccompProfile:
1116 | type: RuntimeDefault
1117 | capabilities:
1118 | drop: ["ALL"]
1119 | allowPrivilegeEscalation: false
1120 | readOnlyRootFilesystem: true
1121 | # image-renderer deployment Host Aliases
1122 | hostAliases: []
1123 | # image-renderer deployment priority class
1124 | priorityClassName: ""
1125 | service:
1126 | # Enable the image-renderer service
1127 | enabled: true
1128 | # image-renderer service port name
1129 | portName: "http"
1130 | # image-renderer service port used by both service and deployment
1131 | port: 8081
1132 | targetPort: 8081
1133 | # Adds the appProtocol field to the image-renderer service. This allows to work with istio protocol selection. Ex: "http" or "tcp"
1134 | appProtocol: ""
1135 | serviceMonitor:
1136 | ## If true, a ServiceMonitor CRD is created for a prometheus operator
1137 | ## https://github.com/coreos/prometheus-operator
1138 | ##
1139 | enabled: false
1140 | path: /metrics
1141 | # namespace: monitoring (defaults to use the namespace this chart is deployed to)
1142 | labels: {}
1143 | interval: 1m
1144 | scheme: http
1145 | tlsConfig: {}
1146 | scrapeTimeout: 30s
1147 | relabelings: []
1148 | # See: https://doc.crds.dev/github.com/prometheus-operator/kube-prometheus/monitoring.coreos.com/ServiceMonitor/v1@v0.11.0#spec-targetLabels
1149 | targetLabels:
1150 | []
1151 | # - targetLabel1
1152 | # - targetLabel2
1153 | # If https is enabled in Grafana, this needs to be set as 'https' to correctly configure the callback used in Grafana
1154 | grafanaProtocol: http
1155 | # In case a sub_path is used this needs to be added to the image renderer callback
1156 | grafanaSubPath: ""
1157 | # name of the image-renderer port on the pod
1158 | podPortName: http
1159 | # number of image-renderer replica sets to keep
1160 | revisionHistoryLimit: 10
1161 | networkPolicy:
1162 | # Enable a NetworkPolicy to limit inbound traffic to only the created grafana pods
1163 | limitIngress: true
1164 | # Enable a NetworkPolicy to limit outbound traffic to only the created grafana pods
1165 | limitEgress: false
1166 | # Allow additional services to access image-renderer (eg. Prometheus operator when ServiceMonitor is enabled)
1167 | extraIngressSelectors: []
1168 | resources: {}
1169 | # limits:
1170 | # cpu: 100m
1171 | # memory: 100Mi
1172 | # requests:
1173 | # cpu: 50m
1174 | # memory: 50Mi
1175 | ## Node labels for pod assignment
1176 | ## ref: https://kubernetes.io/docs/user-guide/node-selection/
1177 | #
1178 | nodeSelector: {}
1179 |
1180 | ## Tolerations for pod assignment
1181 | ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
1182 | ##
1183 | tolerations: []
1184 |
1185 | ## Affinity for pod assignment (evaluated as template)
1186 | ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
1187 | ##
1188 | affinity: {}
1189 |
1190 | ## Use an alternate scheduler, e.g. "stork".
1191 | ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/
1192 | ##
1193 | # schedulerName: "default-scheduler"
1194 |
1195 | networkPolicy:
1196 | ## @param networkPolicy.enabled Enable creation of NetworkPolicy resources. Only Ingress traffic is filtered for now.
1197 | ##
1198 | enabled: false
1199 | ## @param networkPolicy.allowExternal Don't require client label for connections
1200 | ## The Policy model to apply. When set to false, only pods with the correct
1201 | ## client label will have network access to grafana port defined.
1202 | ## When true, grafana will accept connections from any source
1203 | ## (with the correct destination port).
1204 | ##
1205 | ingress: true
1206 | ## @param networkPolicy.ingress When true enables the creation
1207 | ## an ingress network policy
1208 | ##
1209 | allowExternal: true
1210 | ## @param networkPolicy.explicitNamespacesSelector A Kubernetes LabelSelector to explicitly select namespaces from which traffic could be allowed
1211 | ## If explicitNamespacesSelector is missing or set to {}, only client Pods that are in the networkPolicy's namespace
1212 | ## and that match other criteria, the ones that have the good label, can reach the grafana.
1213 | ## But sometimes, we want the grafana to be accessible to clients from other namespaces, in this case, we can use this
1214 | ## LabelSelector to select these namespaces, note that the networkPolicy's namespace should also be explicitly added.
1215 | ##
1216 | ## Example:
1217 | ## explicitNamespacesSelector:
1218 | ## matchLabels:
1219 | ## role: frontend
1220 | ## matchExpressions:
1221 | ## - {key: role, operator: In, values: [frontend]}
1222 | ##
1223 | explicitNamespacesSelector: {}
1224 | ##
1225 | ##
1226 | ##
1227 | ##
1228 | ##
1229 | ##
1230 | egress:
1231 | ## @param networkPolicy.egress.enabled When enabled, an egress network policy will be
1232 | ## created allowing grafana to connect to external data sources from kubernetes cluster.
1233 | enabled: false
1234 | ##
1235 | ## @param networkPolicy.egress.ports Add individual ports to be allowed by the egress
1236 | ports: []
1237 | ## Add ports to the egress by specifying - port:
1238 | ## E.X.
1239 | ## ports:
1240 | ## - port: 80
1241 | ## - port: 443
1242 | ##
1243 | ##
1244 | ##
1245 | ##
1246 | ##
1247 | ##
1248 |
1249 | # Enable backward compatibility of kubernetes where version below 1.13 doesn't have the enableServiceLinks option
1250 | enableKubeBackwardCompatibility: false
1251 | useStatefulSet: false
1252 | # Create a dynamic manifests via values:
1253 | extraObjects:
1254 | []
1255 | # - apiVersion: "kubernetes-client.io/v1"
1256 | # kind: ExternalSecret
1257 | # metadata:
1258 | # name: grafana-secrets
1259 | # spec:
1260 | # backendType: gcpSecretsManager
1261 | # data:
1262 | # - key: grafana-admin-password
1263 | # name: adminPassword
1264 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 | Prism - K8s visualizer
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/jest.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "testEnvironment": "jsdom",
3 | "collectCoverage": true,
4 | "collectCoverageFrom": ["**/**/*.{ts,tsx}"],
5 | "coverageReporters": ["json-summary", "text-summary"]
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "prism",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "NODE_ENV=development webpack-dev-server --mode development --open",
8 | "build": "webpack",
9 | "dev": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --open --hot --progress --color \" \"nodemon ./server/server.ts\"",
10 | "watch": "npx tailwindcss -i ./client/input.css -o ./client/output.css --watch",
11 | "test": "jest",
12 | "coverage": "jest --coverage"
13 | },
14 | "keywords": [],
15 | "author": "",
16 | "license": "ISC",
17 | "dependencies": {
18 | "@types/react-icons": "^3.0.0",
19 | "@types/supertest": "^2.0.12",
20 | "axios": "^1.4.0",
21 | "bcrypt": "^5.1.0",
22 | "bootstrap": "^5.3.0",
23 | "concurrently": "^8.2.0",
24 | "cookie-parser": "^1.4.6",
25 | "cross-env": "^7.0.3",
26 | "dotenv": "^16.3.1",
27 | "express": "^4.18.2",
28 | "js-cookie": "^3.0.5",
29 | "js-cookies": "^1.0.4",
30 | "jsonwebtoken": "^9.0.1",
31 | "mongoose": "^7.3.1",
32 | "nodemon": "^2.0.22",
33 | "pg": "^8.11.1",
34 | "postcss-preset-env": "^9.0.0",
35 | "psql": "^0.0.1",
36 | "react": "^18.2.0",
37 | "react-dom": "^18.2.0",
38 | "react-icons": "^4.10.1",
39 | "react-iframe": "^1.8.5",
40 | "react-router": "^6.14.0",
41 | "react-router-dom": "^6.14.2",
42 | "saslprep": "^1.0.3",
43 | "style-loader": "^3.3.3",
44 | "ts-node": "^10.9.1",
45 | "url": "^0.11.1"
46 | },
47 | "devDependencies": {
48 | "@babel/core": "^7.22.9",
49 | "@babel/preset-env": "^7.22.9",
50 | "@babel/preset-react": "^7.22.5",
51 | "@babel/preset-typescript": "^7.22.5",
52 | "@jest/globals": "^29.6.2",
53 | "@savvywombat/tailwindcss-grid-areas": "^3.1.0",
54 | "@testing-library/dom": "^9.3.1",
55 | "@testing-library/jest-dom": "^5.17.0",
56 | "@testing-library/react": "^14.0.0",
57 | "@testing-library/user-event": "^14.4.3",
58 | "@types/bcrypt": "^5.0.0",
59 | "@types/cookie-parser": "^1.4.3",
60 | "@types/cors": "^2.8.13",
61 | "@types/jest": "^29.5.3",
62 | "@types/js-cookie": "^3.0.3",
63 | "@types/jsonwebtoken": "^9.0.2",
64 | "autoprefixer": "^10.4.14",
65 | "babel-loader": "^9.1.2",
66 | "babel-preset-react": "^6.24.1",
67 | "cors": "^2.8.5",
68 | "css-loader": "^6.8.1",
69 | "eslint-plugin-jest": "^27.2.2",
70 | "file-loader": "^x.x.x",
71 | "html-webpack-plugin": "^5.5.3",
72 | "jest": "^29.6.2",
73 | "jest-environment-jsdom": "^29.6.2",
74 | "msw": "^1.2.3",
75 | "nodemon": "^2.0.22",
76 | "path": "^0.12.7",
77 | "postcss": "^8.4.24",
78 | "postcss-loader": "^7.3.3",
79 | "react-testing-library": "^8.0.1",
80 | "sass": "^1.63.6",
81 | "sass-loader": "^13.3.2",
82 | "supertest": "^6.3.3",
83 | "tailwindcss": "^3.3.3",
84 | "ts-jest": "^29.1.1",
85 | "ts-loader": "^9.4.4",
86 | "typescript": "^5.1.6",
87 | "url-loader": "^4.1.1",
88 | "webpack": "^5.88.1",
89 | "webpack-cli": "^5.1.4",
90 | "webpack-dev-server": "^4.15.1"
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | const tailwindcss = require('tailwindcss');
2 | //import tailwindcss from 'tailwindcss';
3 | module.exports = {
4 | plugins: ['postcss-preset-env', tailwindcss('./tailwind.config.js')],
5 | };
6 |
--------------------------------------------------------------------------------
/prism_logo_square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/prism_logo_square.png
--------------------------------------------------------------------------------
/readme-gifs/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/readme-gifs/demo.gif
--------------------------------------------------------------------------------
/readme-gifs/demo_darkmode.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/readme-gifs/demo_darkmode.gif
--------------------------------------------------------------------------------
/readme-gifs/demo_login.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/readme-gifs/demo_login.gif
--------------------------------------------------------------------------------
/readme-gifs/demo_signup.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/readme-gifs/demo_signup.gif
--------------------------------------------------------------------------------
/readme-gifs/demo_views.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Prism/ad41d215dc7a3c28aff7d8c3a811eee33c6f484c/readme-gifs/demo_views.gif
--------------------------------------------------------------------------------
/server/controllers/metricsController.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import { Controller, middlewareError } from '../../types/types';
4 |
5 | interface apiKeyObject {
6 | id?: number;
7 | name?: string;
8 | key?: string;
9 | message?: string;
10 | }
11 |
12 | const readAPIKey = (): apiKeyObject => {
13 | // we're reading the api token from a file here. we could store it in a cookie instead, but
14 | return JSON.parse(
15 | fs
16 | .readFileSync(path.resolve(__dirname, '../../grafana/api_token.json'))
17 | .toString()
18 | );
19 | };
20 |
21 | const metricsController: Controller = {
22 | // read api key so we can send requests
23 | // save in res.locals for now
24 | // createDashboard
25 | createDashboard: (req, res, next) => {
26 | // save api Key
27 | const apiKeyObj: apiKeyObject = readAPIKey();
28 | const apiKey: string | undefined = apiKeyObj.key;
29 |
30 | // if api key comes back undefined, something is wrong
31 | if (!apiKey) {
32 | const error: middlewareError = {
33 | log: `Error occurred in createDashboard middleware: ${apiKeyObj.message}`,
34 | status: 424,
35 | };
36 | return next(error);
37 | }
38 |
39 | const dashboardJSON: Object = JSON.parse(
40 | fs
41 | .readFileSync(
42 | path.resolve(__dirname, '../../grafana/dashboards/mvp_dashboard.json')
43 | )
44 | .toString()
45 | );
46 |
47 | const dashboardObject: Object = {
48 | dashboard: dashboardJSON,
49 | overwrite: true,
50 | };
51 | // res.locals.dashboard = dashboardJSON;
52 |
53 | // post fetch request with authorization header
54 | const dashboardProvision: string = JSON.stringify(dashboardObject);
55 |
56 | fetch('http://localhost:3000/api/dashboards/db', {
57 | method: 'POST',
58 | headers: {
59 | Accept: 'application/json',
60 | 'Content-Type': 'application/json',
61 | Authorization: `Bearer ${apiKey}`,
62 | },
63 | body: dashboardProvision,
64 | })
65 | .then((response) => response.json())
66 | .then((data) => {
67 | if (!data.url) return Promise.reject(new Error(data.message));
68 | res.locals.dashboardURL = data.url;
69 |
70 | return next();
71 | })
72 | .catch((err) =>
73 | next({
74 | log: 'Error occurred in createDashboard middleware',
75 | status: 424,
76 | message: { error: err },
77 | })
78 | );
79 | },
80 |
81 | writeDashboardURL: (req, res, next) => {
82 | // only write the dashboard if it doesn't already exist as a cookie - and save it as one
83 | if (res.locals.dashboardURL) {
84 | // set url to session cookie instead of writing to file
85 | res.cookie('url', res.locals.dashboardURL, { maxAge: 60 * 60 * 1000 });
86 | }
87 |
88 | return next();
89 | },
90 |
91 | readDashboardURL: (req, res, next) => {
92 | if (req.cookies.url) res.locals.dashboardURL = req.cookies.url;
93 |
94 | res.locals.urlSaved = res.locals.dashboardURL
95 | ? res.locals.dashboardURL.length > 0
96 | : false;
97 |
98 | return next();
99 | },
100 |
101 | // get dashboard iframe info
102 | getDashboardIframeURL: (req, res, next) => {
103 | // make fetch request for dashboard to get its url
104 | // this can be source for iframe
105 |
106 | const dashboardURL: string = res.locals.dashboardURL;
107 |
108 | // convert total dashboard import to frame-by-frame
109 | res.locals.iframe = {
110 | frameURL: `http://localhost:3000${dashboardURL.replace(
111 | '/d/',
112 | '/d-solo/'
113 | )}?panelId=1`,
114 | };
115 |
116 | return next();
117 | },
118 | };
119 |
120 | export default metricsController;
121 |
--------------------------------------------------------------------------------
/server/controllers/userController.ts:
--------------------------------------------------------------------------------
1 | // user authentication middleware
2 | import User, { HydratedDocument, IUser } from '../db/models/userSchema'; // import user model
3 | import jwt, { JwtPayload } from 'jsonwebtoken';
4 | import { Controller, LocalUser as LocalUser } from '../../types/types';
5 | // mvp of this stretch feature: basic user auth, lasts while window is open
6 | // stretch feature level 1: sets a JWT in cookie to use for auth purposes
7 | // stretch feature level
8 | interface jwtPayload extends JwtPayload {
9 | username?: string;
10 | token?: string;
11 | }
12 | const userController: Controller = {};
13 |
14 | // create a new user in database with signup field
15 | userController.createUser = async function (req, res, next) {
16 | // username nand password should come in on response body
17 | const { username, password } = req.body;
18 | console.log(username, password);
19 | const existingUser: HydratedDocument | null = await User.findOne({
20 | username,
21 | });
22 |
23 | console.log('user exists? ', existingUser); // show when user doesn't exist
24 | if (!existingUser) {
25 | const user: HydratedDocument = await User.create({
26 | username,
27 | password,
28 | });
29 | res.locals.user = { username: user.username, created: true };
30 | res.status(201);
31 | } else {
32 | res.status(202);
33 | res.locals.user = { created: false };
34 | }
35 | return next();
36 | };
37 |
38 | // authenticate user by comparing password with database hash
39 | userController.authUser = async function (req, res, next) {
40 | // if user is already token authenticated, we can continue
41 | const usr: LocalUser = req.body;
42 | if (usr.auth) return next();
43 | // username nand password should come in on response body
44 | const { username, password } = req.body;
45 | const user: HydratedDocument | null = await User.findOne({ username });
46 | // authenticate passsword with comparison method on user model
47 | res.locals.user = {
48 | username: username,
49 | auth: user && (await user.matchPassword(password)),
50 | };
51 | res.status(res.locals.user.auth ? 200 : 401);
52 |
53 | return next();
54 | };
55 |
56 | // setToken : create JWT for authenticated users
57 | // use this for authorization
58 | userController.setUserToken = function (req, res, next) {
59 | // set JWT only if eitther user has successfully been created (signup) or authenticated (signin)
60 | const usr: LocalUser = res.locals.user;
61 | if (usr.auth || usr.created) {
62 | res.locals.jwt = jwt.sign(
63 | { username: usr.username },
64 | process.env.JWT_SECRET ?? 'vaticancameos',
65 | {
66 | expiresIn: 3600, // set expiry to 1 hour
67 | }
68 | );
69 | // store token in cookie
70 | res.cookie('userToken', res.locals.jwt, {
71 | maxAge: 3600000, // one hour
72 | secure: process.env.NODE_ENV !== 'development',
73 | });
74 | }
75 | return next();
76 | };
77 |
78 | // verifyToken: verify authentication token
79 | userController.verifyUserToken = (req, res, next) => {
80 | // if there's no token, the user isn't logged in yet or the cookie has been deleted
81 | if (!req.cookies.token) {
82 | res.locals.user = { auth: false, message: 'missing token' };
83 | return next();
84 | }
85 |
86 | // verify token
87 |
88 | try {
89 | const decodedToken: jwtPayload | string = jwt.verify(
90 | req.cookies.token,
91 | process.env.JWT_SECRET ?? 'vaticancameos'
92 | ) as jwtPayload;
93 | if (decodedToken.username) {
94 | res.locals.user = { username: decodedToken.username, auth: true };
95 | } else {
96 | res.locals.user = { auth: false, message: 'TokenInvalid' };
97 | }
98 | } catch (err) {
99 | if (err.name === 'TokenExpiredError') {
100 | res.locals.user = { auth: false, message: 'TokenExpired' };
101 | } else {
102 | return next({
103 | message: { err: 'An error occured ' },
104 | log: 'Error occurred in verifying JWT in verifyToken middleware',
105 | error: err,
106 | });
107 | }
108 | } finally {
109 | return next();
110 | }
111 | };
112 |
113 | userController.setOauthToken = function (req, res, next) {
114 | console.log('entering setOauthToken middleware');
115 | res.locals.jwt = jwt.sign(
116 | { gitUser: res.locals.token },
117 | process.env.JWT_SECRET ?? 'vaticancameos',
118 | {
119 | expiresIn: 3600, // set expiry to 1 hour
120 | }
121 | );
122 | // store token in cookie
123 | console.log('right before cookie');
124 | res.cookie('oauthToken', res.locals.jwt, {
125 | maxAge: 3600000, // one hour
126 | secure: process.env.NODE_ENV !== 'development',
127 | });
128 | console.log('after cookie: ', res.locals.jwt);
129 | return next();
130 | };
131 |
132 | userController.verifyOauthToken = function (req, res, next) {
133 | // read payload from jwt.
134 | try {
135 | res.locals.token = (
136 | jwt.verify(
137 | req.cookies['oauthToken'],
138 | process.env.JWT_SECRET ?? 'vaticancameos'
139 | ) as jwtPayload
140 | )['token'];
141 | } catch (err) {
142 | console.log(err);
143 | }
144 |
145 | return next();
146 | };
147 | // The deleteUser middleware here is mainly used for testing.
148 | userController.deleteUser = async (req, res, next) => {
149 | const { username } = req.body;
150 | const usr: LocalUser = res.locals.user;
151 | // deletion authorization is tied to either the password or the jwt to prevent unauthorized users from using this method.
152 | const authorized: boolean = usr.auth === true && usr.username === username;
153 | if (!authorized) {
154 | res.locals.user = {
155 | username: username,
156 | deleted: false,
157 | message: 'insufficient permissions',
158 | };
159 | return next();
160 | }
161 | const deleted: boolean =
162 | (await User.deleteOne({ username: username })).deletedCount === 1;
163 |
164 | res.locals.user = { username: username, deleted: deleted };
165 | return next();
166 | };
167 |
168 | export default userController;
169 |
--------------------------------------------------------------------------------
/server/db/db.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 | import dotenv from 'dotenv';
3 |
4 | dotenv.config();
5 |
6 | const connectDB = async (): Promise => {
7 | try {
8 | const connection = await mongoose.connect(process.env.MONGO_URI);
9 | console.log(`MongoDB is connected to: ${connection.connection.host}`);
10 | } catch (error) {
11 | console.log(error.message);
12 | }
13 | };
14 |
15 | export default connectDB;
16 |
--------------------------------------------------------------------------------
/server/db/models/userSchema.ts:
--------------------------------------------------------------------------------
1 | // user authentication
2 | import mongoose, { HydratedDocument } from 'mongoose';
3 | const { Schema } = mongoose;
4 | import bcrypt from 'bcrypt';
5 | interface IUser {
6 | username: string;
7 | password: string;
8 | matchPassword: (inputPassword: string) => boolean;
9 | }
10 | const userSchema = new Schema({
11 | username: String,
12 | password: String,
13 | });
14 |
15 | // pre-save to hash password
16 | userSchema.pre('save', async function (next) {
17 | // if password has not been modified , we don't need to hash
18 | // (this is for user object updates)
19 | console.log('checking if document is modified: ', this.password, this.isModified(this.password))
20 | if (!this.isModified(this.password) && !this.isNew) return next();
21 | const salt: string = await bcrypt.genSalt(10);
22 | console.log("Salt: ",salt);
23 | const hashedPassword: string = await bcrypt.hash(this.password, salt);
24 | this.password = hashedPassword;
25 | });
26 |
27 | // method for comparing passwords stored on user schema
28 | userSchema.methods.matchPassword = async function (inputPassword: string) {
29 | return await bcrypt.compare(inputPassword, this.password);
30 | };
31 | // create model from schema to export
32 | const userModel = mongoose.model('users', userSchema);
33 |
34 | export default userModel;
35 |
36 | export { HydratedDocument, IUser };
37 |
--------------------------------------------------------------------------------
/server/routers/apiRouter.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 | import metricsController from '../controllers/metricsController';
3 | const router: Router = Router();
4 | // get api key
5 | // when authorization is implemented here, we should route certain api calls through verifytoken middleware
6 | // routes :
7 |
8 | // create dashboard
9 | router.post(
10 | '/',
11 | metricsController.readDashboardURL,
12 | (req, res, next) => {
13 | if (res.locals.dashboardURL) {
14 | // do nothing
15 | return next();
16 | } else {
17 | return metricsController.createDashboard(req, res, next);
18 | }
19 | },
20 | metricsController.writeDashboardURL,
21 | metricsController.getDashboardIframeURL,
22 | (req, res) => res.status(200).json(res.locals.iframe)
23 | );
24 |
25 | // get dashboard panel with specific id (currently not in use )
26 | router.get(
27 | '/dashboard',
28 | metricsController.readDashboardURL,
29 | metricsController.getDashboardIframeURL,
30 | (req, res) => res.json(res.locals.iframe)
31 | );
32 |
33 | // router.get('/install', installPrometheus, installGrafana, (req, res) => {
34 | // return res.status(200).send('installed');
35 | // });
36 |
37 | export default router;
38 |
--------------------------------------------------------------------------------
/server/routers/userRouter.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 | import userController from '../controllers/userController';
3 |
4 | // destructure imports into variables
5 | const router: Router = Router();
6 | const { createUser, authUser, setUserToken, deleteUser, verifyUserToken } =
7 | userController; // destructure imports
8 |
9 | router.post('/signup', createUser, setUserToken, (req, res) => {
10 | // set cookie
11 | // res.cookie('user', res.locals.user.username, { maxAge: 3600000 }); // level 1 authentication: regular cookie
12 | res.json(res.locals.user);
13 | });
14 |
15 | router.post('/login', authUser, setUserToken, (req, res) => {
16 | res.json(res.locals.user);
17 | });
18 |
19 | router.delete('/', verifyUserToken, authUser, deleteUser, (req, res) => {
20 | res.json(res.locals.user);
21 | });
22 |
23 | // oauth
24 | router.get(
25 | '/getAccessToken',
26 | async (req, res, next) => {
27 | // console.log(req.query.code);
28 |
29 | const params =
30 | '?client_id=' +
31 | process.env.CLIENT_ID +
32 | '&client_secret=' +
33 | process.env.CLIENT_SECRET +
34 | '&code=' +
35 | req.query.code;
36 |
37 | await fetch('https://github.com/login/oauth/access_token' + params, {
38 | method: 'POST',
39 | headers: {
40 | Accept: 'application/json',
41 | },
42 | })
43 | .then((response) => response.json())
44 | .then((data) => {
45 | res.locals.token = data.access_token;
46 | // res.json(data);
47 | return next();
48 | });
49 | },
50 | userController.setOauthToken,
51 | (req, res) => {
52 | return res.status(200).json({ auth: true });
53 | }
54 | );
55 |
56 | // getUserData
57 | router.get('/getUserData', async function (req, res) {
58 | await fetch('https://api.github.com/user', {
59 | method: 'GET',
60 | headers: {
61 | Authorization: res.locals.token as string,
62 | },
63 | })
64 | .then((response) => response.json())
65 | .then((data) => {
66 | res.json(data);
67 | });
68 | });
69 |
70 | export default router;
71 |
--------------------------------------------------------------------------------
/server/server.ts:
--------------------------------------------------------------------------------
1 | // types import
2 | import { middlewareError } from '../types/types';
3 | // express server setup
4 | import express, { NextFunction, Request, Response, json } from 'express';
5 | import cookieParser from 'cookie-parser';
6 | import cors from 'cors';
7 | import { resolve } from 'path';
8 |
9 | // route imports
10 | import apiRouter from './routers/apiRouter';
11 | import userRouter from './routers/userRouter';
12 |
13 | // database connection
14 | import connectDB from './db/db';
15 |
16 | // const declarations
17 | const app = express();
18 | app.use(cors());
19 | const PORT = 3333; // from josh's branch
20 | connectDB(); /// uncomment to connect to DB
21 |
22 | // parse request body
23 | app.use(json());
24 | app.use(cors());
25 | app.use(cookieParser());
26 |
27 | // serve static files (just CSS right now)
28 | // app.use(express.static('client')) // from josh
29 | app.use('/api', apiRouter);
30 | app.use('/user', userRouter);
31 | // just to get something running
32 | app.get('/', (req, res) => {
33 | return res.status(200).sendFile(resolve(__dirname, '../index.html'));
34 | });
35 |
36 | // catch all route
37 | app.get('*', (req, res) => {
38 | return res.status(404).send('Page Not Found!');
39 | });
40 |
41 | // global error handler
42 | app.use(
43 | (err: middlewareError, req: Request, res: Response, next: NextFunction) => {
44 | const defaultErr: middlewareError = {
45 | log: 'Express error handler caught unknown middleware error',
46 | status: 500,
47 | message: { error: 'An error occurred' },
48 | };
49 | const errorObj = Object.assign({}, defaultErr, err);
50 | console.log(errorObj.log);
51 | return res.status(errorObj.status).json(errorObj.message);
52 | }
53 | );
54 | // hi
55 | app.listen(PORT, () => {
56 | console.log(`App listening on PORT ${PORT}`);
57 | });
58 |
59 | export default app;
60 |
--------------------------------------------------------------------------------
/startup.zsh:
--------------------------------------------------------------------------------
1 |
2 | # Install dependencies
3 | npm install
4 |
5 | # install and deploy prometheus
6 | helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
7 | helm install prometheus prometheus-community/prometheus
8 | kubectl expose service prometheus-server --type=NodePort --target-port=9090 --name=prometheus-server-np
9 | # minikube service prometheus-server-np --url &
10 |
11 | # install and deploy grafana
12 | helm repo add grafana https://grafana.github.io/helm-charts
13 | helm install -f grafana/config_values.yaml grafana grafana/grafana
14 | kubectl expose service grafana --type=NodePort --target-port=3000 --name=grafana-np
15 | # pot forward : kubectl --namespace default port-forward $POD_NAME 3000
16 | # minikube service grafana-np --url &
17 | sleep 60
18 | while true; do kubectl port-forward deployment/grafana 3000; done 2>&1 &
19 | sleep 5
20 |
21 | curl -X POST -H "Content-Type: application/json" -d '{"name":"apikeycurl0", "role": "Admin"}' http://admin:$(kubectl get secret --namespace default grafana -o jsonpath="{.data.admin-password}" | base64 --decode)@localhost:3000/api/auth/keys > grafana/api_token.json
22 |
23 | npm run dev
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ['./client/**/*.{js,jsx,ts,tsx}'],
4 | darkMode: 'class', // false or 'media'
5 | theme: {
6 | extend: {
7 | dropShadow: {
8 | '2xl': '6px 5px 5px rgba(0, 0, 0, 0.20)',
9 | '3xl': '6px 5px 5px rgba(0, 0, 0, 0.5)',
10 | '4xl': [
11 | '0 35px 35px rgba(0, 0, 0, 0.25)',
12 | '0 45px 65px rgba(0, 0, 0, 0.15)',
13 | ],
14 | },
15 | spacing: {
16 | 0.1: '0.1rem', // Define your custom padding value
17 | },
18 | fontFamily: {
19 | nunito: ['Nunito Sans', 'sans-serif'],
20 | roboto: ['Roboto', 'sans-serif'],
21 | },
22 | gridTemplateAreas: {
23 | layout: [
24 | 'sidebar header header header header header',
25 | 'sidebar main main main main main ',
26 | 'sidebar main main main main main',
27 | 'sidebar main main main main main',
28 | 'sidebar main main main main main',
29 | 'sidebar main main main main main',
30 | 'sidebar main main main main main',
31 | 'sidebar main main main main main',
32 | 'sidebar main main main main main',
33 | 'sidebar main main main main main',
34 | ],
35 | },
36 | gridTemplateColumns: {
37 | layout: '1fr 1fr 1fr 1fr 1fr 1fr',
38 | },
39 | gridTemplateRows: {
40 | layout: '1fr 1fr 1fr 1fr,1fr,1fr,1fr,1fr,1fr,1fr',
41 | },
42 | },
43 | },
44 | plugins: [require('@savvywombat/tailwindcss-grid-areas')],
45 | };
46 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "CommonJS",
5 | "jsx": "react",
6 | "esModuleInterop": true,
7 | "baseUrl": "./",
8 | // "noEmit": true,
9 | // "allowImportingTsExtensions": true,
10 | "noImplicitAny": true,
11 | "allowJs": true,
12 | "outDir": "./dist"
13 | },
14 | "include": ["./client/**/*", "./types/**/*", "./server/**/*"]
15 |
16 | // "exclude": [
17 | // "./client/assets/**/*",
18 | // ]
19 | // "files": [
20 | // "./client/components/AppIntro.tsx",
21 | // "./client/components/App.tsx",
22 | // ]
23 | }
24 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.jpg";
2 | declare module "*.png";
--------------------------------------------------------------------------------
/types/types.ts:
--------------------------------------------------------------------------------
1 | import { RequestHandler } from 'express';
2 | interface middlewareError {
3 | log: string;
4 | status: number;
5 | message?: { error: string };
6 | }
7 |
8 | interface Controller {
9 | [middlewareFn: string]: RequestHandler;
10 | }
11 |
12 | interface ThemeProps {
13 | initialTheme: string;
14 | children: any;
15 | }
16 |
17 | type LUser = {
18 | username: string;
19 | password?: string;
20 | auth?: boolean;
21 | created?: boolean;
22 | };
23 |
24 | export { middlewareError, Controller, ThemeProps, LUser as LocalUser };
25 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | // import path from 'path';
3 | const HTMLWebpackPlugin = require('html-webpack-plugin');
4 | // import HTMLWebpackPlugin from 'html-webpack-plugin';
5 | // import url from 'url';
6 | // const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
7 | module.exports = {
8 | mode: process.env.NODE_ENV,
9 | entry: path.resolve(__dirname, 'client/index.tsx'),
10 |
11 | output: {
12 | path: path.resolve(__dirname, 'build'),
13 | filename: 'bundle.js',
14 | assetModuleFilename: 'images/[hash][ext][query]',
15 | },
16 |
17 | plugins: [
18 | new HTMLWebpackPlugin({
19 | // filename: 'index.html',
20 | template: '/index.html',
21 | }),
22 | ],
23 |
24 | devServer: {
25 | host: 'localhost',
26 | port: '8080',
27 | hot: true,
28 | proxy: {
29 | '/': 'http://localhost:3333',
30 | secure: false,
31 | changeOrigin: true,
32 | },
33 | historyApiFallback: true,
34 | static: {
35 | directory: path.resolve(__dirname, 'build'),
36 | publicPath: 'build',
37 | },
38 | },
39 |
40 | module: {
41 | rules: [
42 | {
43 | test: /\.tsx?$/,
44 | exclude: /node_modules/,
45 | use: ['ts-loader'],
46 | },
47 |
48 | {
49 | test: /\.(js|jsx)$/,
50 | exclude: /node_modules/,
51 | use: {
52 | loader: 'babel-loader',
53 | options: {
54 | presets: ['@babel/preset-env', '@babel/preset-react'],
55 | },
56 | },
57 | },
58 | {
59 | test: /\.css$/,
60 | use: ['style-loader', 'css-loader', 'postcss-loader'],
61 | },
62 | {
63 | test: /\.png/,
64 | type: 'asset/resource',
65 | },
66 | {
67 | test: /\.svg$/,
68 | type: 'asset/resource',
69 | },
70 | // {
71 | // test: /\\.(png|jp(e*)g|svg|gif)$/,
72 | // use: ['file-loader'],
73 | // },
74 | {
75 | test: /\.(png|jpe?g|svg|gif)$/i,
76 | type: 'asset/resource',
77 | },
78 | ],
79 | },
80 | resolve: {
81 | extensions: ['.tsx', '.ts', '.js', '.jsx', '.png', '.svg'],
82 | },
83 | };
84 |
--------------------------------------------------------------------------------