├── public
├── LogoPNG.png
├── favicon.ico
├── assets
│ └── banner.png
├── backgroundImg.png
├── setupExs
│ ├── containers.png
│ ├── visit-9090.png
│ ├── apply-restart.png
│ ├── helm-repo-add.png
│ ├── helm-repo-update.png
│ ├── kubectl-get-pod.png
│ ├── kubectl-get-pods.png
│ ├── kubectl-version.png
│ ├── minikube-start.jpg
│ ├── status-targets.png
│ ├── brew-install-helm.png
│ ├── kubectl-get-nodes.png
│ ├── helm-install-cluster.png
│ ├── kubectl-get-services.png
│ ├── kubectl-port-forward.png
│ └── brew-install-prometheus.png
├── vercel.svg
├── LICENSE
└── LOGO-FINAL.svg
├── postcss.config.js
├── pages
├── auth
│ ├── style.css
│ └── signin.tsx
├── _app.tsx
├── api
│ ├── user
│ │ ├── login.ts
│ │ └── register.ts
│ └── auth
│ │ └── [...nextauth].ts
├── index.tsx
└── register.tsx
├── cypress
├── fixtures
│ └── example.json
├── support
│ ├── e2e.ts
│ └── commands.ts
└── e2e
│ └── spec.cy.ts
├── cypress.config.ts
├── components
├── dashboard
│ ├── costError.tsx
│ ├── spinner.tsx
│ ├── grafana.tsx
│ ├── dashboardContainer.tsx
│ └── costComponent.tsx
├── input.tsx
├── navbar.tsx
├── login-btn.tsx
├── footer.tsx
└── register.tsx
├── models
└── user.ts
├── old_env
├── .gitignore
├── tailwind.config.js
├── tsconfig.json
├── utils
└── connectMongo.ts
├── typings.d.ts
├── test.txt
├── next.config.js
├── package.json
├── prometheus-prometheus.yaml
├── styles
└── globals.css
├── copy.md
├── constants.ts
└── README.md
/public/LogoPNG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/LogoPNG.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/assets/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/assets/banner.png
--------------------------------------------------------------------------------
/public/backgroundImg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/backgroundImg.png
--------------------------------------------------------------------------------
/public/setupExs/containers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/containers.png
--------------------------------------------------------------------------------
/public/setupExs/visit-9090.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/visit-9090.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/setupExs/apply-restart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/apply-restart.png
--------------------------------------------------------------------------------
/public/setupExs/helm-repo-add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/helm-repo-add.png
--------------------------------------------------------------------------------
/public/setupExs/helm-repo-update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/helm-repo-update.png
--------------------------------------------------------------------------------
/public/setupExs/kubectl-get-pod.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/kubectl-get-pod.png
--------------------------------------------------------------------------------
/public/setupExs/kubectl-get-pods.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/kubectl-get-pods.png
--------------------------------------------------------------------------------
/public/setupExs/kubectl-version.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/kubectl-version.png
--------------------------------------------------------------------------------
/public/setupExs/minikube-start.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/minikube-start.jpg
--------------------------------------------------------------------------------
/public/setupExs/status-targets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/status-targets.png
--------------------------------------------------------------------------------
/public/setupExs/brew-install-helm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/brew-install-helm.png
--------------------------------------------------------------------------------
/public/setupExs/kubectl-get-nodes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/kubectl-get-nodes.png
--------------------------------------------------------------------------------
/public/setupExs/helm-install-cluster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/helm-install-cluster.png
--------------------------------------------------------------------------------
/public/setupExs/kubectl-get-services.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/kubectl-get-services.png
--------------------------------------------------------------------------------
/public/setupExs/kubectl-port-forward.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/kubectl-port-forward.png
--------------------------------------------------------------------------------
/public/setupExs/brew-install-prometheus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/poseidon/HEAD/public/setupExs/brew-install-prometheus.png
--------------------------------------------------------------------------------
/pages/auth/style.css:
--------------------------------------------------------------------------------
1 | .body {
2 | background-image: url('../public/backgroundImg.png');
3 | background-repeat: no-repeat;
4 | backdrop-filter: blur(100px);
5 | }
6 |
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/cypress.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "cypress";
2 |
3 | export default defineConfig({
4 | e2e: {
5 | setupNodeEvents(on:any, config:any) {
6 | // implement node event listeners here
7 | },
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/components/dashboard/costError.tsx:
--------------------------------------------------------------------------------
1 | export default function CostError() {
2 | return (
3 |
4 | You need to add the Kubecost IP address to your .env.local file to see
5 | this data!
6 |
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/components/dashboard/spinner.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import Logo from '../../public/LOGO-FINAL.svg';
3 |
4 | export default function Spinner() {
5 | return (
6 |
7 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import '../styles/globals.css';
2 | import type { AppProps } from 'next/app';
3 | import { SessionProvider } from 'next-auth/react';
4 |
5 | export default function MyApp({
6 | Component,
7 | pageProps: { session, ...pageProps },
8 | }: AppProps) {
9 | return (
10 | /* A wrapper for the next-auth library. */
11 |
12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/models/user.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model, models } from 'mongoose';
2 |
3 | const userSchema = new Schema({
4 | email: { type: String, require: true, unique: true },
5 | password: { type: String, require: true },
6 | firstName: { type: String, require: true },
7 | lastName: { type: String, require: true },
8 | ipAddress: { type: String, require: false },
9 | });
10 |
11 | const User = models.User || model('User', userSchema);
12 |
13 | export default User;
14 |
--------------------------------------------------------------------------------
/old_env:
--------------------------------------------------------------------------------
1 | NODE_ENV=development
2 | MONGO_URI="mongodb+srv://xtchow:poseidon@cluster0.lkfnb5l.mongodb.net/?retryWrites=true&w=majority"
3 | NEXTAUTH_URL=http://localhost:3500
4 | BCRYPT_SALT=10
5 | NEXTAUTH_SECRET=12345678910
6 | LOCAL_CLUSTER_IP = "localhost:3300"
7 | DEPLOYED_CLUSTER_IP = "34.28.48.106"
8 | LOCAL_CLUSTER_NAME = "Jd5xvc04z"
9 | DEPLOYED_CLUSTER_NAME = "3UwLYP04k"
10 | DEPLOYED_CLUSTER_NAME_2 = "eYlLLE0Vk"
11 | KUBECOST_IP = "34.121.148.52"
12 | LOCAL_KUBECOST_IP = "127.0.0.1"
--------------------------------------------------------------------------------
/components/dashboard/grafana.tsx:
--------------------------------------------------------------------------------
1 | export default function Grafana(props: any) {
2 | const { url } = props;
3 | return (
4 |
5 |
6 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | './pages/**/*.{js,ts,jsx,tsx}',
5 | './components/**/*.{js,ts,jsx,tsx}',
6 | './app/**/*.{js,ts,jsx,tsx}',
7 | ],
8 | theme: {
9 | borderWidth: {
10 | DEFAULT: '0.2px',
11 | },
12 | },
13 | fontFamily: {
14 | sans: ['Inter var, sans-serif', { fontFeatureSettings: '"cv11", "ss01"' }],
15 | },
16 | theme: {
17 | container: {
18 | center: true,
19 | },
20 | },
21 | plugins: [require('flowbite/plugin')],
22 | plugins: [],
23 | };
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/utils/connectMongo.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 | import getConfig from 'next/config'
3 |
4 | /* Mongoose has a strict query mode that is enabled by default. This mode requires that you specify a
5 | query filter for all update() and remove() operations. */
6 | mongoose.set('strictQuery', false);
7 |
8 | /* It's getting the server runtime config from the `next.config.js` file. */
9 | const { serverRuntimeConfig } = getConfig()
10 |
11 | /**
12 | * It connects to MongoDB using the URI stored in the serverRuntimeConfig object
13 | */
14 | const connectMongo = async () => mongoose.connect(serverRuntimeConfig.MONGO_URI);
15 |
16 | export default connectMongo;
--------------------------------------------------------------------------------
/cypress/support/e2e.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/e2e.ts is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
--------------------------------------------------------------------------------
/typings.d.ts:
--------------------------------------------------------------------------------
1 | interface LoginValues {
2 | email: string;
3 | password: string;
4 | }
5 | interface KubecostTotals {
6 | totalCPU: number;
7 | totalRAM: number;
8 | totalGPU: number;
9 | totalloadBalancerCost: number;
10 | totalNetworkCost: number;
11 | totalPVCost: number;
12 | totalCost: number;
13 | totalSharedCost: number;
14 | totalDays: number;
15 | key: string;
16 | }
17 | interface HomeProps {
18 | data: {}[];
19 | }
20 | interface RegisterValues {
21 | email: string;
22 | password: string;
23 | lastName: string;
24 | firstName: string;
25 | ipAddress: string;
26 | changePassword?: string;
27 | }
28 | interface SessionData {
29 | email: string;
30 | lastName: string;
31 | firstName: string;
32 | ipAddress: string;
33 | }
--------------------------------------------------------------------------------
/test.txt:
--------------------------------------------------------------------------------
1 | NODE_ENV=development
2 | MONGO_URI="[your mongo db link here]"
3 | NEXTAUTH_URL=http://localhost:3500
4 | BCRYPT_SALT=10
5 | NEXTAUTH_SECRET=[your secret here (any combination of letters or numbers)]
6 | LOCAL_CLUSTER_IP = [your grafana link here: e.g. "localhost:3300"]
7 | DEPLOYED_CLUSTER_IP = [your cloud-deployed ip address here, e.g. "0.0.0.0"]
8 | LOCAL_CLUSTER_NAME = [your local cluster's grafana embed link name (.../d-solo/__your_name__/...)]
9 | DEPLOYED_CLUSTER_NAME = [your deployed cluster's grafana embed link name (.../d-solo/__your_name__/...)]
10 | DEPLOYED_CLUSTER_NAME_2 = [OPTIONAL: any other node in your deployed cluster's grafana embed link name (.../d-solo/__your_name__/...)]
11 | KUBECOST_IP = [your cloud-deployed kubecost ip address here, e.g. "0.0.0.0"]
12 | LOCAL_KUBECOST_IP = [your local kubecost ip address here, e.g. "0.0.0.0"]
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | module.exports = {
3 | serverRuntimeConfig: {
4 | // Will only be available on the server side
5 | MONGO_URI: process.env.MONGO_URI,
6 | // DEPLOYED_CLUSTER_IP: process.env.DEPLOYED_CLUSTER_IP, // Pass through env variables
7 |
8 | // DEPLOYED_CLUSTER_NAME: process.env.DEPLOYED_CLUSTER_NAME,
9 | },
10 | publicRuntimeConfig: {
11 | // Will be available on both server and client
12 | KUBECOST_IP: process.env.KUBECOST_IP,
13 | LOCAL_CLUSTER_IP: process.env.LOCAL_CLUSTER_IP,
14 | LOCAL_CLUSTER_NAME: process.env.LOCAL_CLUSTER_NAME,
15 | DEPLOYED_CLUSTER_IP: process.env.DEPLOYED_CLUSTER_IP,
16 | DEPLOYED_CLUSTER_NAME: process.env.DEPLOYED_CLUSTER_NAME,
17 | DEPLOYED_CLUSTER_NAME_2: process.env.DEPLOYED_CLUSTER_NAME_2,
18 | LOCAL_KUBECOST_IP: process.env.LOCAL_KUBECOST_IP,
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/components/input.tsx:
--------------------------------------------------------------------------------
1 | import { ErrorMessage, useField } from "formik";
2 |
3 | // input component used in forms for register
4 | export const Input = ({ name, label, ...props }: any) => {
5 | const [field, meta] = useField(name);
6 | return (
7 |
8 |
15 |
20 |
21 | );
22 | };
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/public/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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "next dev",
5 | "port": "next dev -p 3500",
6 | "build": "next build",
7 | "start": "next start",
8 | "test": "npx kill-port 3500 && next dev -p 3500 & cypress open",
9 | "forward": "kubectl port-forward --namespace kubecost deployment/kubecost-cost-analyzer 9090 & next dev -p 3500 & kubectl port-forward svc/prometheus-operated 9000:9090 & kubectl --namespace default port-forward svc/minikube-grafana 3300:80"
10 | },
11 | "dependencies": {
12 | "bcrypt": "^5.1.0",
13 | "bcryptjs": "^2.4.3",
14 | "colors": "^1.4.0",
15 | "flowbite": "^1.6.3",
16 | "formik": "^2.2.9",
17 | "mongoose": "^6.8.4",
18 | "next": "latest",
19 | "next-auth": "^4.19.2",
20 | "react": "18.2.0",
21 | "react-dom": "18.2.0",
22 | "swr": "^2.0.3",
23 | "yup": "^0.32.11"
24 | },
25 | "devDependencies": {
26 | "@types/bcryptjs": "^2.4.2",
27 | "@types/node": "18.11.3",
28 | "@types/react": "18.0.21",
29 | "@types/react-dom": "18.0.6",
30 | "autoprefixer": "^10.4.12",
31 | "cypress": "^12.5.1",
32 | "postcss": "^8.4.18",
33 | "tailwindcss": "^3.2.4",
34 | "typescript": "4.9.4"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/components/navbar.tsx:
--------------------------------------------------------------------------------
1 |
2 | import Image from 'next/image';
3 | import Logo from '../public/LOGO-FINAL.svg';
4 | import LoginButton from './login-btn';
5 |
6 | export default function NavBar() {
7 | return (
8 | <>
9 |
34 | >
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/cypress/support/commands.ts:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************
3 | // This example commands.ts shows you how to
4 | // create various custom commands and overwrite
5 | // existing commands.
6 | //
7 | // For more comprehensive examples of custom
8 | // commands please read more here:
9 | // https://on.cypress.io/custom-commands
10 | // ***********************************************
11 | //
12 | //
13 | // -- This is a parent command --
14 | // Cypress.Commands.add('login', (email, password) => { ... })
15 | //
16 | //
17 | // -- This is a child command --
18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
19 | //
20 | //
21 | // -- This is a dual command --
22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
23 | //
24 | //
25 | // -- This will overwrite an existing command --
26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
27 | //
28 | // declare global {
29 | // namespace Cypress {
30 | // interface Chainable {
31 | // login(email: string, password: string): Chainable
32 | // drag(subject: string, options?: Partial): Chainable
33 | // dismiss(subject: string, options?: Partial): Chainable
34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable
35 | // }
36 | // }
37 | // }
--------------------------------------------------------------------------------
/prometheus-prometheus.yaml:
--------------------------------------------------------------------------------
1 | # for local clusters, applies grafana user keys and tokens
2 | apiVersion: monitoring.coreos.com/v1
3 | kind: Prometheus
4 | metadata:
5 | labels:
6 | prometheus: k8s
7 | name: k8s
8 | namespace: default
9 | spec:
10 | alerting:
11 | alertmanagers:
12 | - name: alertmanager-main
13 | namespace: default
14 | port: web
15 | image: quay.io/prometheus/prometheus:v2.22.1
16 | nodeSelector:
17 | kubernetes.io/os: linux
18 | podMonitorNamespaceSelector: {}
19 | podMonitorSelector: {}
20 | probeNamespaceSelector: {}
21 | probeSelector: {}
22 | replicas: 2
23 | resources:
24 | requests:
25 | memory: 400Mi
26 | ruleSelector:
27 | matchLabels:
28 | prometheus: k8s
29 | role: alert-rules
30 | securityContext:
31 | fsGroup: 2000
32 | runAsNonRoot: true
33 | runAsUser: 1000
34 | serviceAccountName: prometheus-k8s
35 | serviceMonitorNamespaceSelector: {}
36 | serviceMonitorSelector: {}
37 | version: v2.22.1
38 | remoteWrite:
39 | - url: 'https://prometheus-us-central1.grafana.net/api/prom/push'
40 | basicAuth:
41 | username:
42 | name: kubepromsecret
43 | key: '750773'
44 | password:
45 | name: kubepromsecret
46 | key: 'eyJrIjoiOWVlMDRiMWI5NTNlYWM4ODY5MTQ5Zjk4MTE2YjRiNWJiYmEwMDQ0OSIsIm4iOiJQb3NlZGlvbiIsImlkIjo3ODQ5NjJ9'
47 | replicaExternalLabelName: '__replica__'
48 | externalLabels: { cluster: 'test' }
49 |
--------------------------------------------------------------------------------
/pages/api/user/login.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 | import connectMongo from '../../../utils/connectMongo';
3 | import User from '../../../models/user';
4 | import type { NextApiRequest, NextApiResponse } from 'next';
5 | import bcrypt from 'bcryptjs';
6 |
7 | export default async function Login(req: NextApiRequest, res: NextApiResponse) {
8 |
9 | /* Connecting to the mongo database. */
10 | await connectMongo();
11 |
12 |
13 | /* Destructuring the request object. */
14 | const { method, body } = req;
15 | const { email, password } = body;
16 |
17 |
18 | /* Checking to see if the email and password are present. */
19 | if (email && password) {
20 | switch (method) {
21 | case 'POST':
22 | try {
23 | /* Finding the user in the database. */
24 | const user = await User.findOne({ email });
25 |
26 | // need to handle incorrect password
27 | if (user && (await bcrypt.compare(password, user.password))) {
28 | return res.json({ user });
29 | } else {
30 | return res.json({ message: 'User not authorized.' });
31 | }
32 | } catch (reason) {
33 | return res.status(500).json({ login: `Error in GET. ${reason}` });
34 | }
35 | default:
36 | return res.status(200).json({ login: 'This route is not set up yet!' });
37 | }
38 | } else {
39 | return res.status(400).json({ login: 'MISSING FEILD' });
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import DashboardContainer from '../components/dashboard/dashboardContainer';
3 | import NavBar from '../components/navbar';
4 | import Footer from '../components/footer';
5 | import { authOptions } from './api/auth/[...nextauth]';
6 | import { getServerSession } from 'next-auth/next';
7 |
8 |
9 | export default function Home() {
10 | return (
11 |
12 |
13 |
Dashboard
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
27 | // this is a server side retreival of the session cookie, which then returns the props to the component above- allows the page to not even render if no cookie
28 | export async function getServerSideProps(context: any) {
29 | // this gets the session cookie from the server
30 | const session = await getServerSession(context.req, context.res, authOptions);
31 |
32 | // if there is no session cookie, redirect them to the signin page - well before page renders
33 | if (!session) {
34 | return {
35 | redirect: {
36 | destination: '/auth/signin',
37 | permanent: false,
38 | },
39 | };
40 | }
41 | return {
42 | props: { session },
43 | };
44 | }
45 |
--------------------------------------------------------------------------------
/pages/api/user/register.ts:
--------------------------------------------------------------------------------
1 | import connectMongo from '../../../utils/connectMongo';
2 | import User from '../../../models/user';
3 | import type { NextApiRequest, NextApiResponse } from 'next';
4 | import bcrypt from 'bcryptjs';
5 |
6 | export default async function Register(
7 | req: NextApiRequest,
8 | res: NextApiResponse
9 | ) {
10 | await connectMongo();
11 | const { method, body } = req;
12 | const { email, password, firstName, lastName, ipAddress} = body;
13 | if (email && password && firstName && lastName && ipAddress) {
14 | switch (method) {
15 | case 'POST':
16 | try {
17 | //Check if user is already in db
18 | const userExist = await User.findOne({ email });
19 |
20 | //hash password for create user
21 | const salt = await bcrypt.genSalt(10);
22 |
23 | // hash the pasword
24 | const hashedPassword: string = await bcrypt.hash(password, salt);
25 | if (userExist) {
26 | return res.json({ user: false });
27 | } else {
28 | const user = await User.create({
29 | email,
30 | password: hashedPassword,
31 | firstName,
32 | lastName,
33 | ipAddress,
34 | });
35 | return res.json({ user });
36 | }
37 |
38 | } catch (reason) {
39 | return res.status(500).json({ login: `Error in GET. ${reason}` });
40 | }
41 | break;
42 | default:
43 | return res
44 | .status(200)
45 | .json({ register: 'This route is not set up yet!' });
46 | }
47 | } else {
48 | return res.status(400).json({ register: 'MISSING FEILD' });
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/components/login-btn.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { useSession, signIn, signOut } from 'next-auth/react';
3 |
4 | //new client side login button that can be used anywhere to login for auth and toggles between sign in / sign out
5 |
6 | export default function LoginButton() {
7 | const { data: session }: any = useSession();
8 |
9 | // if the session exists render the personalized greeting and logout button
10 | if (session) {
11 | return (
12 | <>
13 | Welcome, {session.user?.firstName}
14 |
15 | signOut()}
18 | >
19 | Sign out
20 |
21 | >
22 | );
23 | }
24 |
25 | // if the session does NOT exist, render the signin button
26 | return (
27 | <>
28 | signIn()}
31 | >
32 | Sign in
33 |
34 | >
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @import url('https://fonts.googleapis.com/css?family=Poppins:100,200,300,400,500,600,700,800,900');
6 |
7 | .body {
8 | background-image: url('../public/backgroundImg.png');
9 | background-repeat: no-repeat;
10 | backdrop-filter: blur(100px);
11 | }
12 |
13 | .full-width {
14 | width: 100%;
15 | }
16 | .error {
17 | color: red;
18 | }
19 | .div1 {
20 | margin: 0;
21 | padding: 0;
22 | box-sizing: border-box;
23 | font-family: 'Poppins', sans-serif;
24 | }
25 |
26 | .div2 {
27 | display: flex;
28 | min-height: 8vh;
29 | align-items: center;
30 | justify-content: center;
31 | }
32 |
33 | .content {
34 | position: relative;
35 | }
36 |
37 | .content h2 {
38 | color: #fff;
39 | font-size: 3em;
40 | position: absolute;
41 | transform: translate(-50%, -50%);
42 | }
43 |
44 | .content h2:nth-child(1) {
45 | color: transparent;
46 | -webkit-text-stroke: 2px #0ea5e9;
47 | }
48 |
49 | .content h2:nth-child(2) {
50 | color: #03a9f4;
51 | animation: animate 3s ease-in-out infinite;
52 | }
53 |
54 | @keyframes animate {
55 | 0%,
56 | 100% {
57 | clip-path: polygon(
58 | 0% 45%,
59 | 16% 44%,
60 | 33% 50%,
61 | 54% 60%,
62 | 70% 61%,
63 | 84% 59%,
64 | 100% 52%,
65 | 100% 100%,
66 | 0% 100%
67 | );
68 | }
69 |
70 | 50% {
71 | clip-path: polygon(
72 | 0% 60%,
73 | 15% 65%,
74 | 34% 66%,
75 | 51% 62%,
76 | 67% 50%,
77 | 84% 45%,
78 | 100% 46%,
79 | 100% 100%,
80 | 0% 100%
81 | );
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/pages/register.tsx:
--------------------------------------------------------------------------------
1 | // IN USE
2 |
3 | import Head from 'next/head';
4 | import Image from 'next/image';
5 | import Link from 'next/link';
6 | import RegisterForm from '../components/register';
7 | import Logo from '../public/LOGO-FINAL.svg'
8 |
9 | // MAIN REGISTER PAGE
10 | export default function Register() {
11 | return (
12 |
13 |
14 |
15 |
Sign Up
16 |
17 |
18 |
19 |
20 |
21 |
22 | {/* SPINNING LOGO */}
23 |
30 |
31 | {/* FANCY POSEIDON TEXT */}
32 |
33 |
34 |
35 |
36 |
POSEIDON
37 | POSEIDON
38 |
39 |
40 |
41 |
42 |
43 | {/* REGISTER FORM COMPONENT */}
44 |
45 |
46 |
47 |
48 | {/* HAVE AN ACCOUNT BLOCK */}
49 |
56 |
57 |
58 |
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/pages/api/auth/[...nextauth].ts:
--------------------------------------------------------------------------------
1 | import NextAuth, { NextAuthOptions } from 'next-auth';
2 | import CredentialsProvider from 'next-auth/providers/credentials';
3 | import getConfig from 'next/config';
4 |
5 | const { serverRuntimeConfig } = getConfig();
6 |
7 | export const authOptions: NextAuthOptions = {
8 | // sets the next option as JWT
9 | session: {
10 | strategy: 'jwt',
11 | },
12 | // sets the provider as credentials : ie: username / password
13 | providers: [
14 | CredentialsProvider({
15 | name: 'credentials',
16 |
17 | credentials: {
18 | email: { label: 'Email', type: 'text', placeholder: 'Email' },
19 | password: { label: 'Password', type: 'password' },
20 | },
21 |
22 | // this is related to the sign in page and sign in function
23 | async authorize(credentials, req) {
24 | const { email, password } = credentials as {
25 | email: string;
26 | password: string;
27 | };
28 | // perform your login logic - call the api - user - login
29 | // have to do a full url
30 | const res = await fetch(`http://localhost:3500/api/user/login`, {
31 | method: 'POST',
32 | headers: {
33 | Accept: 'application.json',
34 | 'Content-Type': 'application/json',
35 | },
36 | body: JSON.stringify({ email: email, password: password }),
37 | });
38 |
39 | const { user } = await res.json();
40 |
41 | if (!res.ok) {
42 | throw new Error(user.message);
43 | }
44 | if (!user) {
45 | return null;
46 | }
47 | return user;
48 | },
49 | }),
50 | ],
51 | secret: serverRuntimeConfig.NEXTAUTH_SECRET!,
52 |
53 | pages: {
54 | signIn: '/auth/signin',
55 | },
56 |
57 | callbacks: {
58 | // the why is below in these articles for syntax
59 | // https://cloudcoders.xyz/blog/nextauth-credentials-provider-with-external-api-and-login-page/
60 | // https://stackoverflow.com/questions/64576733/where-and-how-to-change-session-user-object-after-signing-in
61 |
62 | async jwt({ token, user }: any) {
63 | user && (token.user = user);
64 | return token;
65 | },
66 | async session({ session, token }: any) {
67 | const { email, lastName, firstName } = token.user;
68 | session.user = {
69 | email,
70 | lastName,
71 | firstName,
72 | };
73 | return session;
74 | },
75 | },
76 | };
77 |
78 | export default NextAuth(authOptions);
79 |
--------------------------------------------------------------------------------
/copy.md:
--------------------------------------------------------------------------------
1 | [helper resource](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax)
2 |
3 | ### Setup
4 | 1. Install [the Docker desktop app](https://www.docker.com/products/docker-desktop/).
5 | 2. In your terminal, `brew install minikube`.
6 | - `kubectl` should display [kubectl](https://kubernetes.io/docs/reference/kubectl/kubectl/) menu.
7 | - See `kubectl --help` for usage. Find more information [here](https://kubernetes.io/docs/reference/kubectl/).
8 | - `minikube` should display minikube menu.
9 | 3. Open your desktop Docker app.
10 | 1. Click on the settings icon in the top right.
11 | 2. Select "Kubernetes" on the left.
12 | 3. Check the box for “Enable Kubernetes”.
13 | 4. Click "Apply and Restart". [Ex](/public/setupExs/apply-restart.png).
14 | 5. Return to the Docker dashboard by clicking on the X icon in the upper right corner.
15 | 4. In your terminal, `minikube start -p [custom cluster name here]`.
16 | - A container for the cluster you labeled should appear.. [Ex](/public/setupExs/minikube-start.jpg).
17 | - The dashboard in your Docker app should show [this](/public/setupExs/containers.png).
18 | - `kubectl get nodes` displays created node. [Ex](/public/setupExs/kubectl-get-nodes.png).
19 | - Note: the "ROLES" column could say "master" instead of “control-plane”.
20 | - `kubectl version` shows your client and server version. [Ex](/public/setupExs/kubectl-version.png).
21 | - `kubectl get pods` currently shouldn't have anything. [Ex](/public/setupExs/kubectl-get-pods.png).
22 | - `kubectl get services` should have one entry. [Ex](/public/setupExs/kubectl-get-services.png).
23 | 5. Navigate to your desired directory.
24 | 1. `brew install prometheus` installs Prometheus. [Ex](/public/setupExs/brew-install-prometheus.png).
25 | 2. `brew install helm` installs Helm. [Ex](/public/setupExs/brew-install-helm.png).
26 | 3. `helm repo add prometheus-community https://prometheus-community.github.io/helm-charts` adds [the Helm repository](https://prometheus-community.github.io/helm-charts/). [Ex](/public/setupExs/helm-repo-add.png).
27 | - Then `helm repo update`. [Ex](/public/setupExs/helm-repo-update.png).
28 | 4. `helm install [named cluster here] prometheus-community/kube-prometheus-stack`. [Ex](/public/setupExs/helm-install-cluster.png).
29 | - Visit [here](https://github.com/prometheus-operator/kube-prometheus) for instructions on how to create & configure Alertmanager and Prometheus instances using the Operator.
30 | 5. `kubectl get pod` displays all the pods. [Ex](/public/setupExs/kubectl-get-pod.png).
31 | 1. From the results of the above command, copy the last "NAME" entry except for the starting "prometheus-" and trailling "-0" part.
32 | 2. Then `kubectl port-forward svc/[paste it here] 9090`. [Ex](/public/setupExs/kubectl-port-forward.png).
33 | 3. Visit http://localhost:9090 in the browser, to see the Prometheus web interface. [Ex](/public/setupExs/visit-9090.png).
34 | - Select "Status" from the above navigation bar then "Targets", to see a list of pre-configured scrape targets. [Ex](/public/setupExs/status-targets.png).
35 | 6. ...TO BE CONTINUED.
36 |
37 |
--------------------------------------------------------------------------------
/components/footer.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | export default function Footer() {
4 | return (
5 |
6 | {/* CALL TO ACTION, PLEASE VISIT OUR OSP SITE */}
7 |
8 |
9 |
10 | Learn More
11 |
12 | {' '}
13 | About
14 |
15 | {' '}
16 | Poseidon
17 | {' '}
18 |
19 |
20 |
21 |
26 |
27 |
28 | Main Page
29 |
30 |
31 |
32 |
33 |
34 | {/*
35 |
*/}
36 |
37 | {/* LINKS TO EACH INDIVIDUAL LINKEDIN PAGE */}
38 |
80 |
81 | );
82 | }
83 |
--------------------------------------------------------------------------------
/cypress/e2e/spec.cy.ts:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | any visit to http://localhost:3500/ should check cookies
4 | if no cookies immediate direct to /auth/signin
5 | else displays dashboard
6 | */
7 | describe('login page', () => {
8 | beforeEach(() => { cy.visit('localhost:3500/auth/signin'); });
9 | // it('logo is displayed', () => {});
10 | it('POSEIDON is displayed', () => { cy.contains('h2', 'POSEIDON'); });
11 | context('form inputs', () => {
12 | beforeEach(() => { cy.get('form'); });
13 | it('requires email', () => {
14 | cy.contains('Sign In').click()
15 | .get('div').should('contain', 'Please enter your email');
16 | });
17 | it('requires password', () => {
18 | cy.contains('Sign In').click()
19 | .get('div').should('contain', 'Please enter your password');
20 | });
21 | it('entry into dashboard', () => {
22 | cy.get('input[name="email"]').type('josh@email.com')
23 | .get('input[name="password"]').type('password')
24 | .get('button').contains('Sign In').click()
25 | // .log('CHECK INCOMPLETE NEED REGEX URL');
26 | // .url().should('match', 'localhost:3500')
27 | .url().should('include', '/');
28 | // // UI should reflect this user being logged in
29 | // cy.get('h1').should('contain', 'jane.lane')
30 | });
31 | });
32 |
33 | // // it('Need an account?', () => {
34 | // // cy.contains('Need an account?').should('have.attr', 'href', '#/register');
35 | // // });
36 | it('Sign Up button links to /register', () => {
37 | cy.contains('Sign Up').should('have.attr', 'href', '/register');
38 | });
39 |
40 | // it('Upon 3500, checks cookies', () => {
41 | // cy.visit('http://localhost:3500/');
42 | // cy.url().should('include', '/auth/signin');
43 | // });
44 | });
45 | // console.log(see);
46 |
47 |
48 | // BELOW IS HOW AUTH SHOULD LOOK LIKE
49 | // describe('The Login Page', () => {
50 | // beforeEach(() => {
51 | // // reset and seed the database prior to every test
52 | // cy.exec('npm run db:reset && npm run db:seed')
53 |
54 | // // seed a user in the DB that we can control from our tests
55 | // // assuming it generates a random password for us
56 | // cy.request('POST', '/test/seed/user', { username: 'jane.lane' })
57 | // .its('body')
58 | // .as('currentUser')
59 | // })
60 |
61 | // it('sets auth cookie when logging in via form submission', function () {
62 | // // destructuring assignment of the this.currentUser object
63 | // const { username, password } = this.currentUser
64 |
65 | // cy.visit('/login')
66 |
67 | // cy.get('input[name=username]').type(username)
68 | // // {enter} causes the form to submit
69 | // cy.get('input[name=password]').type(`${password}{enter}`)
70 | // // we should be redirected to /dashboard
71 | // cy.url().should('include', '/dashboard')
72 | // // our auth cookie should be present
73 | // cy.getCookie('your-session-cookie').should('exist')
74 | // })
75 | // })
76 |
77 |
78 | describe('register', () => {
79 |
80 | it('no fields may be empty', () => {
81 | cy.visit('localhost:3500/register')
82 | .get('input[name="firstName"]').should('have.value', '')
83 | .get('input[name="lastName"]').should('have.value', '')
84 | .get('input[name="email"]').should('have.value', '')
85 | .get('input[name="password"]').should('have.value', '')
86 | .get('input[name="changePassword"]').should('have.value', '')
87 | .get('form').contains('Register').click()
88 | .url().should('include', '/register');
89 | });
90 | // it('lastname can\'t be empty', () => {});
91 | // it('email can\'t be empty', () => {});
92 |
93 | });
--------------------------------------------------------------------------------
/constants.ts:
--------------------------------------------------------------------------------
1 | import getConfig from 'next/config';
2 | const { publicRuntimeConfig } = getConfig();
3 |
4 | export const name_local: string = publicRuntimeConfig.LOCAL_CLUSTER_NAME;
5 | export const name_deployed: string = publicRuntimeConfig.DEPLOYED_CLUSTER_NAME;
6 | export const name_deployed_2: string =
7 | publicRuntimeConfig.DEPLOYED_CLUSTER_NAME_2;
8 |
9 | const IP_local_env: string = publicRuntimeConfig.LOCAL_CLUSTER_IP;
10 | const IP_deployed_env: string = publicRuntimeConfig.DEPLOYED_CLUSTER_IP;
11 |
12 | export const dashUrls: string[][] = [
13 | // ARRAY FOR DEPLOYED CLUSTER
14 | [
15 | `http://${IP_deployed_env}/d-solo/${name_deployed_2}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=2`,
16 | `http://${IP_deployed_env}/d-solo/${name_deployed_2}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=3`,
17 | `http://${IP_deployed_env}/d-solo/${name_deployed_2}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=4`,
18 | `http://${IP_deployed_env}/d-solo/${name_deployed_2}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=5`,
19 | `http://${IP_deployed_env}/d-solo/${name_deployed_2}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=6`,
20 | `http://${IP_deployed_env}/d-solo/${name_deployed_2}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=10`,
21 | `http://${IP_deployed_env}/d-solo/${name_deployed_2}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=8`,
22 | `http://${IP_deployed_env}/d-solo/${name_deployed_2}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=9`,
23 | `http://${IP_deployed_env}/d-solo/${name_deployed}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=2`,
24 | `http://${IP_deployed_env}/d-solo/${name_deployed}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=3`,
25 | `http://${IP_deployed_env}/d-solo/${name_deployed}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=4`,
26 | `http://${IP_deployed_env}/d-solo/${name_deployed}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=5`,
27 | `http://${IP_deployed_env}/d-solo/${name_deployed}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=6`,
28 | `http://${IP_deployed_env}/d-solo/${name_deployed}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=7`,
29 | `http://${IP_deployed_env}/d-solo/${name_deployed}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=8`,
30 | `http://${IP_deployed_env}/d-solo/${name_deployed}/node-exporter-nodes?orgId=1&refresh=30s&from=now-2h&to=now&panelId=9`,
31 | ],
32 |
33 | // ARRAY FOR LOCAL CLUSTER (MINIKUBE), grafana iframe embed links example
34 | [
35 | `http://${IP_local_env}/d-solo/${name_local}/node-exporter-nodes?orgId=1&refresh=30s&from=now-24h&to=now&panelId=2`,
36 | `http://${IP_local_env}/d-solo/${name_local}/node-exporter-nodes?orgId=1&refresh=30s&from=now-24h&to=now&panelId=3`,
37 | `http://${IP_local_env}/d-solo/${name_local}/node-exporter-nodes?orgId=1&refresh=1d&from=now-24h&to=now&panelId=4`,
38 | `http://${IP_local_env}/d-solo/${name_local}/node-exporter-nodes?orgId=1&refresh=1d&from=now-24h&to=now&panelId=5`,
39 | `http://${IP_local_env}/d-solo/${name_local}/node-exporter-nodes?orgId=1&refresh=1d&from=now-24h&to=now&panelId=6`,
40 | `http://${IP_local_env}/d-solo/${name_local}/node-exporter-nodes?orgId=1&refresh=1d&from=now-24h&to=now&panelId=7`,
41 | `http://${IP_local_env}/d-solo/${name_local}/node-exporter-nodes?orgId=1&refresh=1d&from=now-24h&to=now&panelId=8`,
42 | `http://${IP_local_env}/d-solo/${name_local}/node-exporter-nodes?orgId=1&refresh=1d&from=now-24h&to=now&panelId=9`,
43 | ],
44 | ];
45 |
--------------------------------------------------------------------------------
/components/register.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { Formik, Field, Form, FormikHelpers } from 'formik';
3 | import { useRouter } from 'next/navigation';
4 | import * as Yup from "yup";
5 | import { Input } from './input';
6 |
7 | export default function RegisterForm() {
8 | const router = useRouter();
9 |
10 | const loginUser = async (body: RegisterValues) => {
11 | try {
12 | const res = await fetch(`/api/user/register`, {
13 | method: 'POST',
14 | headers: {
15 | Accept: 'application.json',
16 | 'Content-Type': 'application/json',
17 | },
18 | body: JSON.stringify(body),
19 | });
20 | // if data present - redirect to dashboard
21 | const { user } = await res.json();
22 | // if data present - redirect to dashboard
23 | if (user) {
24 | // may need to route to login to confirm they know their information
25 | router.push(`/`);
26 | } else {
27 | // user is not in the db - route to sign up page
28 | // NEED TO ADD MESSAGE EXPLAINING WHAT TO DO + WHY
29 | router.push(`/login`);
30 | }
31 | } catch (err) {
32 | console.log(err);
33 | }
34 | };
35 |
36 | return (
37 |
38 |
39 |
40 |
41 |
42 | Register
43 |
44 |
(val && val.length > 0 ? true : false),
65 | then: Yup.string().oneOf(
66 | [Yup.ref("password")],
67 | "Both password need to be the same"
68 | )
69 | }),
70 | lastName: Yup.string()
71 | .required("Required")
72 | .max(30, 'Must be 30 characters or less'),
73 | firstName: Yup.string()
74 | .required("Required")
75 | .max(30, 'Must be 30 characters or less'),
76 | ipAddress: Yup.string()
77 | .required("Required")
78 | .max(30, 'Must be 30 characters or less'),
79 | })}
80 |
81 | // unsure about this error
82 | onSubmit={(
83 | values: RegisterValues,
84 | { setSubmitting }: FormikHelpers
85 | ) => {
86 | loginUser({
87 | email: values.email,
88 | password: values.password,
89 | firstName: values.firstName,
90 | lastName: values.lastName,
91 | ipAddress: values.ipAddress,
92 | });
93 | setSubmitting(false);
94 | }}
95 | >
96 |
169 |
170 |
171 |
172 |
173 | );
174 | }
175 |
--------------------------------------------------------------------------------
/components/dashboard/dashboardContainer.tsx:
--------------------------------------------------------------------------------
1 | import { dashUrls } from '../../constants';
2 | import CostComponent from './costComponent';
3 | import Grafana from './grafana';
4 | import { useState, useEffect } from 'react';
5 | import useSWR from 'swr';
6 | import CostError from './costError';
7 | import Spinner from './spinner';
8 | import getConfig from 'next/config';
9 | const { publicRuntimeConfig } = getConfig();
10 |
11 | const fetcher = async (url: string) => fetch(url).then((res) => res.json());
12 | export default function DashboardContainer() {
13 | const [clusterType, setClusterType] = useState('deployed');
14 | const kubeip = publicRuntimeConfig.KUBECOST_IP;
15 | const localKubeip = publicRuntimeConfig.KUBECOST_IP;
16 | const localClusterIp = publicRuntimeConfig.LOCAL_CLUSTER_IP;
17 | const deployedClusterIp = publicRuntimeConfig.DEPLOYED_CLUSTER_IP;
18 |
19 | const { data, error, isLoading } = useSWR(
20 | `http://${kubeip}:9090/model/allocation?window=15d&aggregate=cluster`,
21 | fetcher
22 | );
23 | let deployedVisualizers;
24 | // array for deployed clusters
25 | if (dashUrls[0][0]) {
26 | deployedVisualizers = dashUrls[0][0]
27 | ? dashUrls[0].map((url, i) => )
28 | : [
29 |
30 | You need to connect deployed clusters to see this data!
31 |
,
32 | ];
33 | }
34 |
35 | // array for local clusters
36 | const localVisualizers = dashUrls[1][0]
37 | ? dashUrls[1].map((url, i) => )
38 | : [
39 |
43 | You need to connect local clusters to see this data!
44 |
,
45 | ];
46 |
47 | const toggleSelection = (e: React.SyntheticEvent) => {
48 | e.preventDefault();
49 | setClusterType(e.currentTarget.id);
50 | };
51 |
52 | useEffect(() => {
53 | let clusterIndex = clusterType === 'deployed' ? 0 : 1;
54 | }, [clusterType]);
55 |
56 | return (
57 |
58 |
59 |
60 |
64 |
65 |
74 | Deployed Cluster
75 |
76 |
85 | Local Cluster
86 |
87 |
96 | Cost Analysis
97 |
98 |
99 |
100 |
101 | {/* insert both dataURL[0] and dataURL[1] with either grafana tags or the div*/}
102 |
109 | {/*@ts-ignore*/}
110 | {deployedClusterIp ? (
111 | deployedVisualizers
112 | ) : (
113 | <>
114 |
115 | You need to add deployed cluster IP address to .env.local file
116 | to see this data!
117 |
118 | >
119 | )}
120 |
121 |
128 | {localClusterIp ? (
129 | <>{localVisualizers}>
130 | ) : (
131 | <>
132 |
133 | You need to add local cluster IP address to .env.local file to
134 | see this data!
135 |
136 | >
137 | )}
138 |
139 |
146 |
147 |
148 |
149 |
150 |
151 | );
152 | }
153 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ## [](https://www.os-poseidon.com/)
5 |
6 |
7 | [](https://nextjs.org/)
8 | [](https://reactjs.org/)
9 | [](https://www.typescriptlang.org/)
10 | [](https://grafana.com/)
11 | [](https://kubernetes.io/)
12 | [](https://www.docker.com/)
13 | [](https://prometheus.io/)
14 | [](https://tailwindcss.com/)
15 | [](https://www.mongodb.com/)
16 | [](https://www.cypress.io/)
17 | [](public/LICENSE)
18 |
19 | ### This Cluster Data Visualizer and Cost Analysis app delivers cost estimates, stunning visualizations, and crucial insights to optimize deployments and stay within budget.
20 |
21 |
22 |
23 |
24 |
25 | [](https://medium.com/@drjoshdpt/poseidon-a-kubernetes-cluster-visualization-cost-analysis-tool-d0fb55c2858c)
26 | [](https://youtu.be/hqeGqh0ksPg)
27 | [](https://www.linkedin.com/company/osposeidon/)
28 |
29 |
30 |
31 | Quick Start
32 |
33 | 1. Fork this repository to your profile, clone it to your local machine, navigate into the directory, then run `npm install`.
34 | 2. Ensure your local and/or cloud clusters are deployed, then within the root directory, create a `.env.local` file and paste in the following:
35 | ```
36 | NODE_ENV = development
37 | MONGO_URI = "[REPLACE THIS WITH YOUR MONGODB CONNECTION STRING]"
38 | NEXTAUTH_URL = http://localhost:3500
39 | BCRYPT_SALT = 10
40 | NEXTAUTH_SECRET = 12345678910
41 | KUBECOST_IP = "[REPLACE THIS WITH YOUR DEPLOYED KUBECOST IP ADDRESS]"
42 | LOCAL_KUBECOST_IP = "[REPLACE THIS WITH YOUR LOCAL KUBECOST IP ADDRESS]"
43 | LOCAL_CLUSTER_IP = "localhost:3300"
44 | LOCAL_CLUSTER_NAME = "[REPLACE THIS WITH THE UNIQUE EMBED ID FROM YOUR LOCAL CLUSTER'S GRAFANA SHARE LINK]"
45 | DEPLOYED_CLUSTER_IP = "[REPLACE THIS WITH YOUR DEPLOYED* CLUSTER IP ADDRESS]"
46 | DEPLOYED_CLUSTER_NAME = "[REPLACE THIS WITH THE UNIQUE EMBED ID FROM YOUR CLOUD HOSTED GRAFANA SHARE LINK]"
47 | DEPLOYED_CLUSTER_NAME_2 = "[OPTIONAL: REPLACE THIS WITH THE UNIQUE EMBED ID FROM YOUR CLOUD HOSTED GRAFANA SHARE LINK]"
48 | ```
49 | - Be sure to replace the areas of all caps text including the enclosing square brackets with the specified information, then save the file.
50 | - *Functions for both depolyments on AWS and/or Google.
51 | - For more detailed instructions, please visit our website [os-poseidon.com/setup](https://www.os-poseidon.com/setup).
52 | 3. Regarding your command options:
53 | - `npm run port` runs the application on port 3500 specifically for cloud deployments.
54 | - `npm run forward` runs the application on port 3500 for both local and cloud deployments.
55 | - `npm run test` assures that any processes on port 3500 is killed then in parallel runs the application on that port and opens Cypress.
56 |
57 |
58 |
59 | ## Current Features
60 | - Cloud and local K8s cluster support
61 | - Cluster metric visualization
62 | - Cost-analysis via Kubecost
63 |
64 | ## Iteration Plans
65 | - [ ] Apply a regex functionality to identify the id name from embed urls and provide an input field in the registration form to store cluster IP addresses for the client's ease of use.
66 | - [ ] Connect a feature to directly customize cluster specifics
67 | - [ ] Implement an authentication hierarchy to allow for role and privilege assignments. This secures and regulates any manipulations to an organization's clusters.
68 | - [ ] Create a filter for the graphs on the dashboard for cluster and data types.
69 | - [ ] Integrate D3.js for graph styling.
70 | - [ ] Move forward with AWS hosting for online deployment of the app which allows for greater scalability and dependability.
71 | - [ ] Develop a notification system within the application to enable users to receive notifications, such as through the Slack API, in case their clusters experiences downtime.
72 | - [ ] Improve visual comfort and reduce eye strain by implementing dark mode and including dynamic display scaling.
73 |
74 |
75 |
76 |
86 |
87 | ## Connect with the Team!
88 | | Joshuah Edwards | Sarah Chow | Alina Grafkina | Will Moody |
89 | | :---: | :---: | :---: | :---: |
90 | | [](https://github.com/JoshDPT) [](https://www.linkedin.com/in/joshuah-edwards/) | [](https://github.com/xtchow) [](https://www.linkedin.com/in/xtchow/) | [](https://github.com/alyagraf) [](https://www.linkedin.com/in/alina-grafkina-955a56179/) | [](https://github.com/wmoody6293) [](https://www.linkedin.com/in/william-moody/) |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/pages/auth/signin.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { signIn, getCsrfToken } from 'next-auth/react';
3 | import { Formik, Field, ErrorMessage } from 'formik';
4 | import * as Yup from 'yup';
5 | import { useRouter } from 'next/router';
6 | import Head from 'next/head';
7 | import Image from 'next/image';
8 | import Logo from '../../public/LOGO-FINAL.svg';
9 | export default function SignIn({ csrfToken }: any): JSX.Element {
10 | const router = useRouter();
11 | const [error, setError] = useState(null);
12 |
13 | return (
14 | <>
15 |
16 | Sign In
17 |
18 |
19 |
20 | {
36 | const res: any = await signIn('credentials', {
37 | redirect: false,
38 | email: values.email,
39 | password: values.password,
40 | callbackUrl: `${window.location.origin}`,
41 | });
42 | if (res?.error) {
43 | setError(res.error);
44 | } else {
45 | setError(null);
46 | }
47 | if (res.url) router.push(res.url);
48 | setSubmitting(false);
49 | }}
50 | >
51 | {(formik) => (
52 |
155 | )}
156 |
157 | >
158 | );
159 | }
160 |
161 | // This is the recommended way for Next.js 9.3 or newer
162 | export async function getServerSideProps(context: any) {
163 | return {
164 | props: {
165 | csrfToken: await getCsrfToken(context),
166 | },
167 | };
168 | }
169 |
--------------------------------------------------------------------------------
/components/dashboard/costComponent.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import useSWR from 'swr';
3 | import getConfig from 'next/config';
4 | import Spinner from './spinner';
5 | const { publicRuntimeConfig } = getConfig();
6 | const fetcher = async (url: string) => fetch(url).then((res) => res.json());
7 |
8 | export default function CostComponent({ deployedCost }: any) {
9 | let refinedData: any = '';
10 | let tableVals: any = '';
11 | let dataArr: any;
12 | if (deployedCost) {
13 | dataArr = deployedCost.data;
14 | }
15 | const kubeip = publicRuntimeConfig.LOCAL_KUBECOST_IP;
16 | const { data, error, isLoading } = useSWR(
17 | `http://${kubeip}:9090/model/allocation?window=15d&aggregate=cluster`,
18 | fetcher
19 | );
20 | const [kubeCostVals, setKubeCostVals] = useState({});
21 | const [clusterType, setClusterType] = useState('deployed');
22 | const [localClusters, setLocalClusters] = useState([]);
23 | useEffect(() => {
24 | const sortFetchedData = async (parsedArr: any) => {
25 | const getProps = async (parsedArr: []) => {
26 | let totalCPU = 0;
27 | let totalRAM = 0;
28 | let totalGPU = 0;
29 | let totalloadBalancerCost = 0;
30 | let totalNetworkCost = 0;
31 | let totalPVCost = 0;
32 | let totalCost = 0;
33 | let totalSharedCost = 0;
34 | let totalDays = 0;
35 |
36 | // add cost efficiency
37 | function getMetrics(metricObject: any) {
38 | for (const key in metricObject) {
39 | if (key === 'cpuCost') totalCPU += metricObject[key];
40 | if (key === 'gpuCost') totalGPU += metricObject[key];
41 | if (key === 'ramCost') totalRAM += metricObject[key];
42 | if (key === 'networkCost') totalNetworkCost += metricObject[key];
43 | if (key === 'loadBalancerCost')
44 | totalloadBalancerCost += metricObject[key];
45 | if (key === 'pvCost') totalPVCost += metricObject[key];
46 | if (key === 'totalCost') totalCost += metricObject[key];
47 | if (key === 'sharedCost') totalSharedCost += metricObject[key];
48 | }
49 | }
50 | parsedArr.forEach((obj: any) => {
51 | //this will ignore any objects that are empty (if cluster only has a few days worth of data)
52 | if (obj.__idle__) {
53 | totalDays += 1;
54 | for (const key in obj) {
55 | getMetrics(obj[key]);
56 | }
57 | }
58 | });
59 | const data = {
60 | 'CPU Cost': totalCPU,
61 | 'RAM Cost': totalRAM,
62 | 'GPU Cost': totalGPU,
63 | 'Load Balancer Cost': totalloadBalancerCost,
64 | 'Network Cost': totalNetworkCost,
65 | 'PV Cost': totalPVCost,
66 | 'Shared Cost': totalSharedCost,
67 | 'Total Cost': totalCost,
68 | totalDays,
69 | };
70 | setKubeCostVals(data);
71 | };
72 | getProps(parsedArr);
73 | };
74 | if (clusterType === 'deployed' && deployedCost) {
75 | sortFetchedData(dataArr);
76 | } else if (clusterType === 'local' && !error) {
77 | console.log('data sortFetchedData: ', data);
78 | sortFetchedData(data.data);
79 | }
80 | }, [setKubeCostVals, dataArr, clusterType, data, getAverages, makeTableVals]);
81 |
82 | function makeTableVals(data: any): void {
83 | if (data) {
84 | const newTableVals = [];
85 | for (const key in data) {
86 | if (data[key] !== 0 && key !== 'totalDays') {
87 | newTableVals.push(
88 |
92 |
96 | {key === 'Total Cost' ? (
97 | {key}:
98 | ) : (
99 | key
100 | )}
101 |
102 |
109 | ${data[key].toFixed(2)}
110 |
111 |
112 | );
113 | }
114 | }
115 | tableVals = newTableVals;
116 | }
117 | }
118 | function getAverages(data: any) {
119 | if (data) {
120 | //take totalDays
121 | const totalDays = data.totalDays;
122 | //create new set of variables to store updated projected costs for each val in data
123 | const averageCost: any = {};
124 |
125 | //iterate over the rest of data
126 | for (const key in data) {
127 | //calc the average cost/day/component
128 | averageCost[key] = (data[key] / totalDays) * 30;
129 | }
130 | refinedData = averageCost;
131 | return;
132 | }
133 | }
134 |
135 | const toggleSelection = (e: React.SyntheticEvent) => {
136 | e.preventDefault();
137 | setClusterType(e.currentTarget.id);
138 | };
139 | if (clusterType === 'deployed' && deployedCost !== undefined) {
140 | getAverages(kubeCostVals);
141 | makeTableVals(refinedData);
142 | } else if (clusterType === 'local' && !error) {
143 | getAverages(kubeCostVals);
144 | makeTableVals(refinedData);
145 | }
146 | return (
147 |
148 |
149 |
150 |
151 |
152 |
153 | Cost
154 | Category
155 |
156 |
157 | Total Cost
158 | per month
159 |
160 |
161 |
162 |
163 | {tableVals ? (
164 | tableVals
165 | ) : (
166 |
167 |
171 |
172 | You Need To Connect To Kubecost
173 | Before You Can See Expected Cluster Costs
174 |
175 |
176 |
177 |
178 | You Need To Connect To Kubecost
179 | Before You Can See Expected Cluster Costs
180 |
181 |
182 |
183 | )}
184 |
185 |
186 |
187 |
188 |
189 | All cost figures are monthly estimates based on data retrieved from
190 | Kubecost.
191 |
192 |
193 |
194 |
198 |
199 |
208 | Deployed Cluster
209 |
210 |
219 | Local Cluster
220 |
221 |
222 |
223 |
224 | );
225 | }
226 |
--------------------------------------------------------------------------------
/public/LOGO-FINAL.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
--------------------------------------------------------------------------------