├── .vscode └── extensions.json ├── src ├── app │ ├── 404 │ │ └── page.jsx │ ├── secret.js │ ├── about │ │ └── page.jsx │ ├── layout.jsx │ ├── _app.jsx │ ├── providers.jsx │ ├── login │ │ └── page.jsx │ ├── signup │ │ └── page.jsx │ ├── page.jsx │ ├── api │ │ └── auth │ │ │ └── [...nextauth] │ │ │ └── route.js │ ├── clusters │ │ └── page.jsx │ ├── cluster-health │ │ └── page.jsx │ ├── consumer-lag │ │ └── page.jsx │ ├── alerts │ │ └── page.jsx │ └── dashboard │ │ └── page.jsx ├── index.js ├── styles │ ├── cn.js │ ├── cn.ts │ ├── theme.js │ └── globals.css ├── utils │ └── dashboardHandler.js ├── components │ ├── colorModeButton.jsx │ ├── navbar.jsx │ ├── loadingModal.jsx │ ├── graphs │ │ └── graph.jsx │ ├── clusters │ │ ├── navbar │ │ │ ├── searchInput.jsx │ │ │ ├── accountMenu │ │ │ │ ├── logoutModal.jsx │ │ │ │ ├── deleteAccountModal.jsx │ │ │ │ └── changePasswordModal.jsx │ │ │ ├── accountMenu.jsx │ │ │ └── addClusterModal.jsx │ │ ├── logoutModal.jsx │ │ ├── deleteClusterModal.jsx │ │ ├── mainContainer │ │ │ ├── deleteClusterModal.jsx │ │ │ └── editClusterModal.jsx │ │ ├── navbar.jsx │ │ ├── addClusterModal.jsx │ │ ├── editClusterModal.jsx │ │ ├── clusterCard.jsx │ │ ├── mainContainer.jsx │ │ └── menuDrawer.jsx │ ├── alerts │ │ ├── configureCustom.jsx │ │ ├── alertsByMetricGraph.jsx │ │ └── alertHistoryGraph.jsx │ ├── index │ │ └── navbar.jsx │ ├── signupForm.jsx │ ├── loginForm.jsx │ ├── signup │ │ └── signupForm.jsx │ └── login │ │ └── loginForm.jsx ├── store │ ├── user.js │ └── clusters.js └── ui │ ├── navbar-menu.jsx │ └── home-background-animation.jsx ├── grafana-data ├── grafana.db └── alerting │ └── 1 │ └── __default__.tmpl ├── kafka-cluster ├── .DS_Store ├── jmx-exporter │ └── jmx_prometheus_javaagent-0.19.0.jar ├── instruction.txt ├── prometheus.yml ├── dockerfile ├── docker-compose.yml └── kafka.yml ├── public ├── kafka-kare-logo.png ├── kafkakareSignup.gif ├── kafka-kare-logo-v2.png ├── kafka-kare-logo-v3.png ├── kafka-kare-background-v1.png ├── kafka-kare-background-v2.jpg ├── kafka-kare-logo-v3-dark.png ├── kafkakareAddingcluster.gif ├── kafkaKare-creatingEnvfile.gif ├── Logo-Dark-Tranparent-150x150.png ├── Logo-Dark-Tranparent-320x320.png ├── Logo-Dark-Tranparent-Original.png ├── kafka-kare-meerkat-background.png ├── kafkakare-dockercomposeup-d.gif ├── Logo-Light-Transparent- 320x320.png ├── Logo-Light-Transparent-150x150.png ├── Logo-Light-Transparent-Original.png └── kafka-kare-meerkat-background-v2.png ├── .dockerignore ├── configs ├── postcss.config.js ├── next.config.js └── tailwind.config.js ├── grafana ├── provisioning │ ├── dashboards │ │ ├── default.yaml │ │ ├── default.yml │ │ └── home.json │ └── datasources │ │ └── datasource.yml └── datasource.yml ├── Dockerfile-server ├── next.config.js ├── NextAuth ├── env.local └── auth.js ├── docs ├── SLACK_SETUP_GUIDE.md └── SETUP_GUIDE.md ├── prometheus.yml ├── Dockerfile-ui ├── .env.example ├── grafana.ini ├── server ├── routes │ ├── iFrameRoutes.js │ ├── metricsRoutes.js │ ├── testingRoutes.js │ ├── grafanaApiRoutes.js │ ├── oAuthRoutes.js │ ├── settingsRoutes.js │ ├── slackRoutes.js │ ├── authRoutes.js │ └── clustersRoutes.js ├── models │ ├── clusterModel.js │ └── userModel.js ├── controllers │ ├── connectionStringController.js │ ├── iFrameController.js │ ├── testingController.js │ ├── tokenController.js │ ├── settingsController.js │ ├── slackController.js │ ├── oAuthController.js │ └── metricsController.js ├── README.md └── server.js ├── consumer.js ├── Dockerfile-ui-dev ├── producer.js ├── LICENSE ├── webpack.config.js ├── package.json ├── .gitignore ├── docker-compose.yml └── loginForm.jsx /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [] 3 | } -------------------------------------------------------------------------------- /src/app/404/page.jsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return
404!
; 3 | } -------------------------------------------------------------------------------- /src/app/secret.js: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return
Hello Team Nickelhack
; 3 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return
Hello Team Nickelhack
; 3 | } -------------------------------------------------------------------------------- /grafana-data/grafana.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/grafana-data/grafana.db -------------------------------------------------------------------------------- /kafka-cluster/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/kafka-cluster/.DS_Store -------------------------------------------------------------------------------- /src/app/about/page.jsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return
Testing about!
; 3 | } -------------------------------------------------------------------------------- /public/kafka-kare-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/kafka-kare-logo.png -------------------------------------------------------------------------------- /public/kafkakareSignup.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/kafkakareSignup.gif -------------------------------------------------------------------------------- /public/kafka-kare-logo-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/kafka-kare-logo-v2.png -------------------------------------------------------------------------------- /public/kafka-kare-logo-v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/kafka-kare-logo-v3.png -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .git 3 | Dockerfile 4 | Dockerfile-ui 5 | .dockerignore 6 | docker-compose.yml 7 | .next -------------------------------------------------------------------------------- /public/kafka-kare-background-v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/kafka-kare-background-v1.png -------------------------------------------------------------------------------- /public/kafka-kare-background-v2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/kafka-kare-background-v2.jpg -------------------------------------------------------------------------------- /public/kafka-kare-logo-v3-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/kafka-kare-logo-v3-dark.png -------------------------------------------------------------------------------- /public/kafkakareAddingcluster.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/kafkakareAddingcluster.gif -------------------------------------------------------------------------------- /configs/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/kafkaKare-creatingEnvfile.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/kafkaKare-creatingEnvfile.gif -------------------------------------------------------------------------------- /public/Logo-Dark-Tranparent-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/Logo-Dark-Tranparent-150x150.png -------------------------------------------------------------------------------- /public/Logo-Dark-Tranparent-320x320.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/Logo-Dark-Tranparent-320x320.png -------------------------------------------------------------------------------- /public/Logo-Dark-Tranparent-Original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/Logo-Dark-Tranparent-Original.png -------------------------------------------------------------------------------- /public/kafka-kare-meerkat-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/kafka-kare-meerkat-background.png -------------------------------------------------------------------------------- /public/kafkakare-dockercomposeup-d.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/kafkakare-dockercomposeup-d.gif -------------------------------------------------------------------------------- /public/Logo-Light-Transparent- 320x320.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/Logo-Light-Transparent- 320x320.png -------------------------------------------------------------------------------- /public/Logo-Light-Transparent-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/Logo-Light-Transparent-150x150.png -------------------------------------------------------------------------------- /public/Logo-Light-Transparent-Original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/Logo-Light-Transparent-Original.png -------------------------------------------------------------------------------- /public/kafka-kare-meerkat-background-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/public/kafka-kare-meerkat-background-v2.png -------------------------------------------------------------------------------- /grafana/provisioning/dashboards/default.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: 'default' 5 | type: file 6 | options: 7 | path: /etc/grafana/provisioning/dashboards -------------------------------------------------------------------------------- /grafana/provisioning/dashboards/default.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: 'default' 5 | type: file 6 | options: 7 | path: /etc/grafana/provisioning/dashboards -------------------------------------------------------------------------------- /kafka-cluster/jmx-exporter/jmx_prometheus_javaagent-0.19.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Kafka-Kare/HEAD/kafka-cluster/jmx-exporter/jmx_prometheus_javaagent-0.19.0.jar -------------------------------------------------------------------------------- /src/styles/cn.js: -------------------------------------------------------------------------------- 1 | import { ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/dashboardHandler.js: -------------------------------------------------------------------------------- 1 | import { userStore } from "../store/user"; // Import state store for user 2 | import axios from "axios"; // Import axios for making HTTP requests 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/styles/cn.ts: -------------------------------------------------------------------------------- 1 | import { ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /Dockerfile-server: -------------------------------------------------------------------------------- 1 | FROM node:21-alpine 2 | 3 | WORKDIR /app/server 4 | COPY package*.json ./ 5 | COPY . . 6 | RUN npm install 7 | EXPOSE 3001 8 | #will start server in dev mode with nodemon. Not recommended for production 9 | CMD [ "npm","run", "server" ] -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | //may or may not need all of these options 3 | webpack: ( 4 | config, 5 | { buildId, dev, isServer, defaultLoaders, nextRuntime, webpack } 6 | ) => { 7 | return config 8 | } 9 | } -------------------------------------------------------------------------------- /kafka-cluster/instruction.txt: -------------------------------------------------------------------------------- 1 | 1. from your terminal navigate to kafka-cluster folder 2 | 2. build kafka cluster image with 3 | - docker build -t dockerpromkafka:latest . 4 | 3. run docker-compose file with 5 | - docker-compose -f docker-compose.yml up -d -------------------------------------------------------------------------------- /configs/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | //may or may not need all of these options 3 | webpack: ( 4 | config, 5 | { buildId, dev, isServer, defaultLoaders, nextRuntime, webpack } 6 | ) => { 7 | return config 8 | } 9 | } -------------------------------------------------------------------------------- /src/app/layout.jsx: -------------------------------------------------------------------------------- 1 | import { Providers } from './providers' 2 | 3 | export default function RootLayout({ 4 | children, 5 | }) { 6 | return ( 7 | 8 | 9 | {children} 10 | 11 | 12 | ) 13 | } -------------------------------------------------------------------------------- /src/styles/theme.js: -------------------------------------------------------------------------------- 1 | import { extendTheme } from '@chakra-ui/react'; 2 | // import { BackgroundGradientAnimation, Menu, MenuItem } from '../src/ui/*'; 3 | 4 | //if we want to use Chakra UI 5 | const theme = extendTheme({ 6 | initialColorMode: 'light', 7 | useSystemColorMode: false, 8 | } 9 | ); 10 | 11 | export default theme; 12 | -------------------------------------------------------------------------------- /NextAuth/env.local: -------------------------------------------------------------------------------- 1 | //fill in id and secret from the oauth setup for each provider 2 | 3 | //google id and secret 4 | AUTH_GOOGLE_ID=950219203152-vcngmgs4k2i9pn0g9k0ta8kn7a4hk7i5.apps.googleusercontent.com 5 | AUTH_GOOGLE_SECRET=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 6 | 7 | //github if we want to add it 8 | AUTH_GITHUB_ID= 9 | 10 | AUTH_GITHUB_SECRET= -------------------------------------------------------------------------------- /kafka-cluster/prometheus.yml: -------------------------------------------------------------------------------- 1 | # Configuration file specifying how Prometheus should scrape metrics 2 | global: 3 | scrape_interval: 1s 4 | evaluation_interval: 1s 5 | scrape_configs: 6 | - job_name: 'kafka' # Scrape job for Kafka 7 | scheme: http 8 | static_configs: 9 | - targets: ['kafka:7070'] # Pointing to the target Port 7070, which is the port exposed by the JMX Exporter -------------------------------------------------------------------------------- /src/app/_app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../styles/globals.css'; 3 | import { ChakraProvider } from '@chakra-ui/react'; 4 | import theme from '../styles/theme.js'; 5 | 6 | 7 | 8 | const App = ({ Component, pageProps }) => { 9 | return( 10 | 11 | 12 | 13 | )}; 14 | 15 | export default App; 16 | -------------------------------------------------------------------------------- /src/app/providers.jsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React from 'react'; 3 | import '../styles/globals.css'; 4 | import { ChakraProvider } from '@chakra-ui/react'; 5 | import theme from '../styles/theme.js'; 6 | import { SessionProvider } from 'next-auth/react'; 7 | 8 | 9 | export function Providers({ children }) { 10 | return ( 11 | 12 | 13 | {children} 14 | 15 | 16 | ) 17 | }; -------------------------------------------------------------------------------- /docs/SLACK_SETUP_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Slack Setup Guide 2 | 3 | ## Instructions to enable Slack notifications 4 | 1. Access your Slack app at `api.slack.com` 5 | 2. Top right, under `Your Apps`, find and select the app you're working with. 6 | 3. In the sidebar menu of your app's settings page, click on `Incoming Webhooks` 7 | 4. Toggle `Active Incoming Webhooks` 8 | 5. At the bottom, click `Add New Webhook to Workspace` 9 | 6. This is your `SLACK_WEBHOOK_URL` that you will need to enter in metricsController.js -------------------------------------------------------------------------------- /src/components/colorModeButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useColorMode, Button } from '@chakra-ui/react'; 3 | 4 | const ColorMode = () => { 5 | 6 | const { colorMode, toggleColorMode } = useColorMode(); 7 | 8 | return ( 9 |
10 | 13 |
14 | ) 15 | } 16 | 17 | export default ColorMode; -------------------------------------------------------------------------------- /prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | evaluation_interva: 15s 4 | 5 | scrape_configs: 6 | - job_name: 'kafka-kare' 7 | static_configs: 8 | - targets: ['kafka-kare-server:3001'] 9 | 10 | - job_name: 'kafka-demo' 11 | static_configs: 12 | - targets: ['kafka-demo:9092'] 13 | 14 | # below is what is required with Grafana to write data to it 15 | remote_write: 16 | - url: 17 | basic_auth: 18 | username: 19 | password: -------------------------------------------------------------------------------- /Dockerfile-ui: -------------------------------------------------------------------------------- 1 | # Front UI dockerfile 2 | FROM node:21-alpine 3 | 4 | WORKDIR /usr/src/app 5 | # Copy package.json, package-lock.json, Next.js, Tailwind CSS configuration files 6 | COPY package.json package-lock.json ./ 7 | COPY configs/next.config.js ./ 8 | COPY configs/postcss.config.js ./ 9 | COPY configs/tailwind.config.js ./ 10 | 11 | RUN npm install 12 | 13 | COPY . . 14 | 15 | RUN npm run build 16 | 17 | 18 | EXPOSE 3000 19 | 20 | 21 | # switch back to this if run dev 22 | #CMD ["npm", "start"] 23 | CMD ["npm", "run", "dev"] 24 | 25 | 26 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Secret key for JWT 2 | SECRET_KEY= 3 | 4 | # MongoDB contrainerized with docker 5 | MONGO_DB_USERNAME= 6 | MONGO_DB_PWD= 7 | 8 | # Slack Webhooks URL 9 | SLACK_WEBHOOK_URL= 10 | 11 | # MongoDB locally hosted 12 | # MONGODB_URI= 13 | 14 | # fill in id and secret from the oauth setup for each provider 15 | AUTH_GITHUB_ID= 16 | AUTH_GITHUB_SECRET= 17 | 18 | # google id and secret 19 | AUTH_GOOGLE_ID= 20 | AUTH_GOOGLE_SECRET= 21 | 22 | NEXTAUTH_URL=http://localhost:3000/ 23 | 24 | #run this command to generate a secret key 25 | #openssl rand -base64 32 26 | NEXTAUTH_SECRET= -------------------------------------------------------------------------------- /grafana.ini: -------------------------------------------------------------------------------- 1 | ; [paths] 2 | ; logs = /var/lib/grafana/logs 3 | ; plugins = /var/lib/grafana/plugins 4 | ; provisioning = /etc/grafana/provisioning 5 | 6 | ; [server] 7 | ; domain = localhost 8 | ; root_url = %(protocol)s://%(domain)s:%(http_port)s/grafana/ 9 | 10 | [security] 11 | allow_embedding = true 12 | # x_frame_options is deprecated and not necessary if allow_embedding is true 13 | 14 | 15 | # Set header value for X-Frame-Options, used to prevent clickjacking attacks. 16 | # Recommended values: "deny" (default), "sameorigin", or "allow-from https://example.com/" 17 | ; x_frame_options = "allow-from http://localhost:3000" 18 | 19 | -------------------------------------------------------------------------------- /src/components/navbar.jsx: -------------------------------------------------------------------------------- 1 | "use client"; //unsure what this is for, but was included on Aceternity UI source code 2 | import React from "react"; 3 | import { Menu, MenuItem } from "../ui/navbar-menu"; 4 | import { cn } from "../styles/cn"; 5 | 6 | const Navbar = ({ className }) => { 7 | 8 | return ( 9 |
10 | 11 | 12 | 13 | 14 | 15 |
16 | ); 17 | }; 18 | 19 | export default Navbar; 20 | -------------------------------------------------------------------------------- /kafka-cluster/dockerfile: -------------------------------------------------------------------------------- 1 | # Builds a custom Kafka Docker image that includes the JMX Prometheus Java agent (jmx_prometheus_javaagent-0.19.0.jar) and a Kafka configuration file (kafka.yml for the JMX Exporter). 2 | FROM wurstmeister/kafka:latest 3 | ADD kafka.yml /opt/kafka/libs/kafka.yml 4 | 5 | RUN wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.19.0/jmx_prometheus_javaagent-0.19.0.jar 6 | 7 | RUN cp jmx_prometheus_javaagent-0.19.0.jar /opt/kafka/libs/jmx_prometheus_javaagent.jar 8 | RUN chmod +r /opt/kafka/libs/jmx_prometheus_javaagent.jar 9 | 10 | EXPOSE 7070 11 | # Exposing port 7070 of the kafka cluster for JMX Exporter -------------------------------------------------------------------------------- /src/store/user.js: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | 3 | const initialState = { 4 | username: '', // State to store username 5 | password: '', // State to store password 6 | showPassword: false, // State to toggle password visibility 7 | usernameInvalid: false, // State to manage password validity 8 | usernameErrorMessage: '', // State to store username error message 9 | passwordInvalid: false, // State to manage username validity 10 | passwordErrorMessage: '', // State to store password error message 11 | } 12 | 13 | export const userStore = create((set) => ({ 14 | ...initialState, 15 | reset: () => {set(initialState)}, 16 | })); 17 | -------------------------------------------------------------------------------- /server/routes/iFrameRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const iFrameController = require("../controllers/iFrameController"); 4 | const tokenController = require("../controllers/tokenController"); 5 | 6 | // Route for retrieving cluster's iFrame from database 7 | router.get( 8 | '/:clusterId', 9 | // tokenController.verifyToken, 10 | iFrameController.getIFrame, 11 | (req, res) => { 12 | console.log('Sending iFrame back to client...'); 13 | // return res.status(200).json({message: "Dashboard created from datasource successfully", data: res.locals.data}); 14 | return res.status(200).json(res.locals.iFrame); 15 | } 16 | ); 17 | 18 | module.exports = router; 19 | -------------------------------------------------------------------------------- /consumer.js: -------------------------------------------------------------------------------- 1 | const { Kafka } = require("kafkajs"); 2 | 3 | const kafka = new Kafka({ 4 | clientId: "my-consumer", 5 | brokers: ["localhost:9093"], 6 | }); 7 | 8 | const topic = "test-topic"; 9 | const consumer = kafka.consumer({ groupId: "test-group" }); 10 | 11 | const consumeMessages = async () => { 12 | await consumer.connect(); 13 | await consumer.subscribe({ topic, fromBeginning: true }); 14 | 15 | await consumer.run({ 16 | eachMessage: async ({ topic, partition, message }) => { 17 | console.log({ 18 | partition, 19 | offset: message.offset, 20 | value: message.value.toString(), 21 | }); 22 | }, 23 | }); 24 | }; 25 | 26 | consumeMessages().catch((error) => { 27 | console.error("Error in consumer script", error); 28 | }); 29 | -------------------------------------------------------------------------------- /server/routes/metricsRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const axios = require("axios"); 3 | const router = express.Router(); 4 | const metricsController = require("../controllers/metricsController"); 5 | const tokenController = require("../controllers/tokenController"); 6 | 7 | // WORK-IN-PROGRESS // Currently, does not take clusterId or userId into account 8 | // Route for getting metrics for a specific cluster 9 | router.get( 10 | "/:clusterId", 11 | tokenController.verifyToken, 12 | metricsController.getMetrics, 13 | metricsController.checkAndSendNotification, 14 | (req, res) => { 15 | console.log('Sending metrics back to client...'); 16 | return res.status(200).json(res.locals.graphData); 17 | } 18 | ); 19 | 20 | module.exports = router; 21 | 22 | -------------------------------------------------------------------------------- /server/routes/testingRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const testingController = require("../controllers/testingController"); 4 | 5 | // TESTING // Route for getting ALL users 6 | router.get( 7 | '/users', 8 | testingController.getAllUsers, 9 | (req, res) => { 10 | console.log('TESTING ROUTE: Sending all users back to client...'); 11 | return res.status(200).json(res.locals.allUsers); 12 | }); 13 | 14 | 15 | // TESTING // Route for getting ALL clusters 16 | router.get( 17 | '/clusters', 18 | testingController.getAllClusters, 19 | (req, res) => { 20 | console.log('TESTING ROUTE: Sending all clusters back to client...'); 21 | return res.status(200).json(res.locals.allClusters); 22 | }); 23 | 24 | module.exports = router; -------------------------------------------------------------------------------- /server/routes/grafanaApiRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const tokenController = require("../controllers/tokenController"); 4 | const grafanaApiController = require("../controllers/grafanaApiController"); 5 | 6 | // Route for adding user data source then initializing user's dashboard to Grafana 7 | router.post( 8 | '/create-datasource', 9 | tokenController.verifyToken, 10 | grafanaApiController.addDatasource, 11 | grafanaApiController.createDashboard, 12 | grafanaApiController.displayDashboard, 13 | (req, res) => { 14 | console.log('Sending Grafana dashboard iFrame back to client...'); 15 | // return res.status(200).json({message: "Dashboard created from datasource successfully", data: res.locals.data}); 16 | return res.status(200).json(res.locals.iFrame); 17 | } 18 | ); 19 | 20 | module.exports = router; 21 | -------------------------------------------------------------------------------- /server/routes/oAuthRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const tokenController = require("../controllers/tokenController"); 4 | const oAuthController = require("../controllers/oAuthController"); 5 | 6 | // Route for user sign up/in - Google 7 | router.post( 8 | "/google", 9 | oAuthController.googleCreateUser, 10 | tokenController.issueToken, 11 | (req, res) => { 12 | return res.status(201).json({ message: `User registered successfully: ${res.locals.username} via Google OAuth` }); 13 | } 14 | ) 15 | 16 | // Route for user sign up/in - Github 17 | router.post( 18 | "/github", 19 | oAuthController.githubCreateUser, 20 | tokenController.issueToken, 21 | (req, res) => { 22 | return res.status(201).json({ message: `User registered successfully: ${res.locals.username} via Github OAuth` }); 23 | } 24 | ) 25 | 26 | module.exports = router; -------------------------------------------------------------------------------- /Dockerfile-ui-dev: -------------------------------------------------------------------------------- 1 | # Front UI dockerfile 2 | FROM node:21-alpine 3 | 4 | # WORKDIR /app/ui 5 | #from the docerfile tutorial 6 | WORKDIR /usr/src/app 7 | # Copy package.json, package-lock.json, Next.js, Tailwind CSS configuration files 8 | # Necessary and differs from the backend for several reasons related to how Next.js and Tailwind CSS applications are built and run 9 | COPY package*.json ./ 10 | COPY configs/next.config.js ./ 11 | COPY configs/postcss.config.js ./ 12 | COPY configs/tailwind.config.js ./ 13 | 14 | COPY . . 15 | RUN npm install 16 | #for hot reloading per muticontainer tutorial/ idk if npm install already does this 17 | RUN npm install -g nodemon 18 | #added from multi container todo tutorial/ Runs app as a non-root user 19 | RUN chown -R node /usr/src/app 20 | USER node 21 | 22 | EXPOSE 4000 23 | 24 | #CMD ["npm", "start"] 25 | #testing this from programming with UMA 26 | CMD ["npm", "run", "dev"] -------------------------------------------------------------------------------- /src/components/loadingModal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, ModalOverlay, ModalContent, CircularProgress } from '@chakra-ui/react'; 3 | import { clustersStore } from '../store/clusters'; 4 | 5 | const LoadingModal = () => { 6 | const isLoadingModalOpen = clustersStore(state => state.isLoadingModalOpen); 7 | 8 | return ( 9 | {clustersStore.setState({isLoadingModalOpen: false})}} 12 | > 13 | 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | 24 | export default LoadingModal; 25 | -------------------------------------------------------------------------------- /server/models/clusterModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const clusterSchema = new Schema({ 5 | // Required 6 | name: { 7 | type: String, 8 | required: true, 9 | }, 10 | hostnameAndPort: { 11 | type: String, 12 | required: true, 13 | }, 14 | ownerId: { 15 | type: String, 16 | required: true 17 | }, 18 | // Not required 19 | dateAdded: { 20 | type: Date, 21 | default: Date.now 22 | }, 23 | dateLastVisited: { 24 | type: Date, 25 | default: Date.now 26 | }, 27 | favorite: { 28 | type: Boolean, 29 | default: false 30 | }, 31 | status: { 32 | type: String, 33 | default: 'online' 34 | }, 35 | numberOfBrokers: { 36 | type: Number, 37 | default: 1 38 | }, 39 | grafanaUrl: { 40 | type: String, 41 | default: '' 42 | }, 43 | }); 44 | 45 | const Cluster = mongoose.model("Cluster", clusterSchema); 46 | 47 | module.exports = Cluster; 48 | -------------------------------------------------------------------------------- /producer.js: -------------------------------------------------------------------------------- 1 | const { Kafka } = require("kafkajs"); 2 | const CHANCE_MESSAGE_SEND = 0.6; 3 | 4 | const kafka = new Kafka({ 5 | clientId: "my-producer", 6 | brokers: ["localhost:9093"], 7 | }); 8 | 9 | const topic = "test-topic"; 10 | const producer = kafka.producer(); 11 | 12 | const produceMessages = async () => { 13 | await producer.connect(); 14 | setInterval(async () => { 15 | try { 16 | if (Math.random() < CHANCE_MESSAGE_SEND) { 17 | const message = { value: `Message from producer at ${new Date().toISOString()}` }; 18 | console.log(`Sending message: ${message.value}`); 19 | await producer.send({ 20 | topic, 21 | messages: [message], 22 | }); 23 | } 24 | } catch (error) { 25 | console.error("Error producing message", error); 26 | } 27 | }, 1000); // Send a message every second 28 | }; 29 | 30 | produceMessages().catch((error) => { 31 | console.error("Error in producer script", error); 32 | }); 33 | -------------------------------------------------------------------------------- /server/controllers/connectionStringController.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const connectionStringController = {}; 3 | 4 | /* ------------------------- CHECK CONNECTION STRING ------------------------ */ 5 | connectionStringController.checkConnection = async (req, res, next) => { 6 | console.log("In connectionStringController.checkConnection"); // testing 7 | const { connectionString } = req.body; // Destructure from req.body 8 | const userId = res.locals.userId; // Destructure from prior middleware 9 | 10 | try { 11 | const response = await axios.get(`${connectionString}/api/v1/query`, { 12 | params: {query: 'up'} // simple way to test connectivity 13 | }); 14 | 15 | // Persist online status 16 | console.log('cluster online status: ONLINE'); 17 | res.locals.onlineStatus = 'ONLINE' 18 | 19 | return next(); 20 | } catch (err) { 21 | return next(); 22 | } 23 | } 24 | 25 | 26 | 27 | // Export 28 | module.exports = connectionStringController; 29 | -------------------------------------------------------------------------------- /src/ui/navbar-menu.jsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useState, useRef } from "react"; 3 | import { motion } from "framer-motion"; 4 | import Link from "next/link"; 5 | 6 | //source code from Aceternity UI 7 | 8 | const transition = { 9 | type: "spring", 10 | mass: 0.5, 11 | damping: 11.5, 12 | stiffness: 100, 13 | restDelta: 0.001, 14 | restSpeed: 0.001, 15 | }; 16 | 17 | export const MenuItem = ({ item, href, ...url }) => { 18 | const containerRef = useRef(null); // Use useRef for reference 19 | 20 | return ( 21 |
22 | 23 | {item} 24 | 25 |
26 | ); 27 | }; 28 | 29 | 30 | export const Menu = ({ setActive, children }) => { 31 | return ( 32 | 36 | ); 37 | }; 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/app/login/page.jsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | import { 5 | Flex, 6 | Box, 7 | useColorModeValue 8 | } from "@chakra-ui/react"; 9 | import LoginForm from '../../components/login/loginForm'; // Import the LoginForm component 10 | 11 | const Login = () => { 12 | 13 | // states for properties in different color mode 14 | const loginBGImage = useColorModeValue('/kafka-kare-background-v2.jpg', '/kafka-kare-meerkat-background-v2.png'); 15 | const loginBGSize = useColorModeValue(200, 100); 16 | 17 | return ( 18 | // Flex container to center the content vertically and horizontally 19 | 23 | {/* Render the LoginForm component */} 24 | 25 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | export default Login; -------------------------------------------------------------------------------- /server/routes/settingsRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const settingsController = require("../controllers/settingsController"); 4 | const tokenController = require("../controllers/tokenController"); 5 | 6 | 7 | // WIP // Route for retrieving user's dark mode status 8 | router.get( 9 | '/colorMode', 10 | tokenController.verifyToken, 11 | settingsController.getColor, 12 | (req, res) => { 13 | console.log('Sending color mode and username back to client...'); 14 | return res.status(200).json({ colorMode: res.locals.colorMode, username: res.locals.username }) 15 | } 16 | ) 17 | 18 | 19 | // WIP // Route for toggling user's dark mode status 20 | router.get( 21 | '/colorMode/toggle', 22 | tokenController.verifyToken, 23 | settingsController.toggleColor, 24 | (req, res) => { 25 | console.log('Sending new color mode back to client...'); 26 | return res.status(200).json({ colorMode: res.locals.colorMode, username: res.locals.username }) 27 | } 28 | ) 29 | 30 | module.exports = router; -------------------------------------------------------------------------------- /src/app/signup/page.jsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | import { 5 | Flex, 6 | Box, 7 | useColorModeValue 8 | } from "@chakra-ui/react"; 9 | import SignupForm from '../../components/signup/signupForm'; // Import the SignupForm component 10 | 11 | const Signup = () => { 12 | 13 | // states for properties in different color mode 14 | const loginBGImage = useColorModeValue('/kafka-kare-background-v2.jpg', '/kafka-kare-meerkat-background-v2.png'); 15 | const loginBGSize = useColorModeValue(200, 100); 16 | 17 | return ( 18 | // Flex container to center the content vertically and horizontally 19 | 23 | {/* Render the SignupForm component */} 24 | 25 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | export default Signup; -------------------------------------------------------------------------------- /src/components/graphs/graph.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { GridItem, Button } from "@chakra-ui/react"; 3 | 4 | const Graphs = ({ selectedMetricId, onRemove, showFullDashboard, iFrameUrl }) => { 5 | console.log('iFrameUrl: ', iFrameUrl); // testing 6 | 7 | return ( 8 | <> 9 | {!showFullDashboard && ( 10 | 11 | 16 | 22 | 23 | )} 24 | 25 | ); 26 | }; 27 | 28 | export default Graphs; 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Open Source Labs 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 | -------------------------------------------------------------------------------- /server/routes/slackRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const slackController = require("../controllers/slackController"); 4 | const tokenController = require("../controllers/tokenController"); 5 | 6 | // Route for editing Slack link 7 | router.patch( 8 | "/update", 9 | tokenController.verifyToken, 10 | slackController.updateSlack, 11 | (req, res) => { 12 | return res.status(200).json({ message: "Slack link updated successfully" }); 13 | } 14 | ); 15 | 16 | // Route for deleting Slack link 17 | router.get( 18 | "/delete", 19 | tokenController.verifyToken, 20 | slackController.deleteSlack, 21 | (req, res) => { 22 | return res.status(200).json({ message: "Slack link deleted successfully" }); 23 | } 24 | ); 25 | 26 | // Route for retrieving a Slack link 27 | router.get( 28 | "/", 29 | tokenController.verifyToken, 30 | slackController.getSlack, 31 | (req, res) => { 32 | console.log("Sending slackUrl and username to client..."); 33 | // return res.status(200).json(res.locals.slackUrl); 34 | return res.status(200).json({ slackUrl: res.locals.slackUrl, username: res.locals.username }) 35 | } 36 | ); 37 | 38 | module.exports = router; 39 | -------------------------------------------------------------------------------- /src/components/clusters/navbar/searchInput.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input, InputGroup, InputLeftElement, InputRightElement, IconButton } from '@chakra-ui/react'; 3 | import { Search2Icon, CloseIcon } from '@chakra-ui/icons'; 4 | import { clustersStore } from '../../../store/clusters'; 5 | import { handleClusterSearchValueChange } from '../../../utils/clustersHandler'; 6 | 7 | const SearchInput = () => { 8 | const clusterSearchValue = clustersStore(state => state.clusterSearchValue); 9 | 10 | return ( 11 | 12 | /* Search input */ 13 | 14 | 15 | 16 | 17 | handleClusterSearchValueChange(event.target.value)} 20 | /> 21 | 22 | } 24 | variant='ghost' onClick = {() => handleClusterSearchValueChange('')} 25 | /> 26 | 27 | 28 | ) 29 | } 30 | 31 | export default SearchInput; -------------------------------------------------------------------------------- /src/components/clusters/logoutModal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Button, Modal, ModalOverlay, ModalContent, ModalHeader, ModalFooter, ModalBody, ModalCloseButton, 4 | } from '@chakra-ui/react'; 5 | import { clustersStore } from '../../store/clusters'; 6 | 7 | const LogoutModal = ({ handleLogout }) => { 8 | 9 | // declare state variables 10 | const isLogoutModalOpen = clustersStore(state => state.isLogoutModalOpen); 11 | 12 | return ( 13 | 14 | /* Delete Cluster Modal */ 15 | clustersStore.setState({isLogoutModalOpen: false})} motionPreset='slideInBottom'> 16 | 17 | 18 | Logout 19 | 20 | 21 | Are you sure you want to log out? 22 | 23 | 24 | 25 | {/* Cancel Button */} 26 | 27 | 28 | {/* Delete Button */} 29 | 30 | 31 | 32 | 33 | ); 34 | }; 35 | 36 | export default LogoutModal; -------------------------------------------------------------------------------- /NextAuth/auth.js: -------------------------------------------------------------------------------- 1 | import { NextAuthConfig } from 'next-auth'; //not sure if we need this one? 2 | import NextAuth from 'next-auth'; 3 | import CredentialsProvider from 'next-auth/providers/credentials'; 4 | 5 | import Google from 'next-auth/providers/google'; 6 | import GitHub from 'next-auth/providers/github'; 7 | 8 | const credentialsConfig = CredentialsProvider({ 9 | name: "Credentials", 10 | credentials: { 11 | username: { 12 | label: "Username", 13 | type: "text", 14 | }, 15 | password: { 16 | label: "Passpord", 17 | type: "password", 18 | }, 19 | }, 20 | async authorize(credentials) { 21 | if (credentials.username === '' && credentials.password === '') 22 | return { 23 | name: 'success', 24 | }; 25 | else return console.log('error authorizing credentials') 26 | }, 27 | }); 28 | 29 | 30 | const config = { 31 | providers: [Google, credentialsConfig], 32 | callbacks: { 33 | authorized( { request, auth }) { 34 | const { pathname } = request.nextUrl; 35 | if (pathname === 'middlewareProtected') return !!auth; 36 | return true; 37 | }, 38 | }, 39 | }; 40 | 41 | 42 | export const { handlers, auth, signIn, signOut } = NextAuth(config); -------------------------------------------------------------------------------- /server/controllers/iFrameController.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const Cluster = require("../models/clusterModel.js"); 3 | 4 | const iFrameController = {}; 5 | 6 | /* ------------------------------- GET I-FRAME ------------------------------ */ 7 | iFrameController.getIFrame = async (req, res, next) => { 8 | console.log("In iFrameController.getIFrame"); // testing 9 | // const { clusterId } = req.params; // Destructure from req.params 10 | 11 | // hard code in // testing 12 | const clusterId = '6605f8ad3a826dad6f0072c2'; 13 | 14 | // Search Database 15 | try { 16 | const cluster = await Cluster.findById(clusterId); 17 | console.log('Response from database received'); 18 | 19 | // Error handling 20 | if (!cluster) return res.status(500).send('Error retrieving iFrame'); 21 | 22 | // Visualize cluster 23 | console.log('cluster: ', cluster); 24 | 25 | // Retrieve grafanaUrl 26 | const iFrame = cluster.grafanaUrl; 27 | console.log('iFrame: ', iFrame); 28 | 29 | // Persist data 30 | res.locals.iFrame = iFrame; 31 | return next(); 32 | 33 | } catch (err) { 34 | return next({ 35 | log: `iFrameController.getIFrame: ERROR ${err}`, 36 | status: 500, 37 | message: { err: "Error occurred in iFrameController.getIFrame." }, 38 | }); 39 | } 40 | } 41 | 42 | // Export 43 | module.exports = iFrameController; 44 | -------------------------------------------------------------------------------- /src/app/page.jsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { useState, useRef, useEffect } from "react"; 4 | import Link from "next/link"; 5 | import { Button, Image, Text, Box } from "@chakra-ui/react"; 6 | import { motion } from "framer-motion"; 7 | import BackgroundAnimation from "../ui/home-background-animation"; 8 | 9 | const Home = () => { 10 | 11 | return ( 12 | 13 | 16 | Kafka Kare 25 | {/* */} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default Home; 38 | -------------------------------------------------------------------------------- /src/components/clusters/deleteClusterModal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Button, Modal, ModalOverlay, ModalContent, ModalHeader, ModalFooter, ModalBody, ModalCloseButton, 4 | } from '@chakra-ui/react'; 5 | import { clustersStore } from '../../store/clusters'; 6 | 7 | const DeleteClusterModal = ({ handleDeleteCluster }) => { 8 | 9 | // declare state variables 10 | const oldClusterName = clustersStore((state) => state.oldClusterName); 11 | const deleteClusterID = clustersStore(state => state.deleteClusterID); 12 | const isDeleteClusterOpen = clustersStore(state => state.isDeleteClusterOpen); 13 | 14 | return ( 15 | 16 | /* Delete Cluster Modal */ 17 | clustersStore.setState({isDeleteClusterOpen: false})} motionPreset='slideInBottom'> 18 | 19 | 20 | Delete Cluster: {oldClusterName} 21 | 22 | 23 | Are you sure? You can't undo this action afterwards. 24 | 25 | 26 | 27 | {/* Cancel Button */} 28 | 29 | 30 | {/* Delete Button */} 31 | 32 | 33 | 34 | 35 | ); 36 | }; 37 | 38 | export default DeleteClusterModal; -------------------------------------------------------------------------------- /configs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | //can modify the file extensions as needed 4 | content: ['../src/**/*.{js, ts, jsx, tsx}'], 5 | darkMode: "class", 6 | theme: { 7 | extend: { 8 | animation: { 9 | first: "moveVertical 30s ease infinite", 10 | second: "moveInCircle 20s reverse infinite", 11 | third: "moveInCircle 40s linear infinite", 12 | fourth: "moveHorizontal 40s ease infinite", 13 | fifth: "moveInCircle 20s ease infinite", 14 | }, 15 | keyframes: { 16 | moveHorizontal: { 17 | "0%": { 18 | transform: "translateX(-50%) translateY(-10%)", 19 | }, 20 | "50%": { 21 | transform: "translateX(50%) translateY(10%)", 22 | }, 23 | "100%": { 24 | transform: "translateX(-50%) translateY(-10%)", 25 | }, 26 | }, 27 | moveInCircle: { 28 | "0%": { 29 | transform: "rotate(0deg)", 30 | }, 31 | "50%": { 32 | transform: "rotate(180deg)", 33 | }, 34 | "100%": { 35 | transform: "rotate(360deg)", 36 | }, 37 | }, 38 | moveVertical: { 39 | "0%": { 40 | transform: "translateY(-50%)", 41 | }, 42 | "50%": { 43 | transform: "translateY(50%)", 44 | }, 45 | "100%": { 46 | transform: "translateY(-50%)", 47 | }, 48 | }, 49 | }, 50 | }, 51 | }, 52 | plugins: [], 53 | }; 54 | -------------------------------------------------------------------------------- /docs/SETUP_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Setup Guide 2 | 3 | 4 | ## Instructions to start the application 5 | 1. Build frontend image 6 | ``` 7 | docker build -f Dockerfile-ui -t kafka-kare-ui . 8 | ``` 9 | 10 | 2. Build backend image 11 | ``` 12 | docker build -f Dockerfile-server -t kafka-kare-server . 13 | ``` 14 | 15 | 3. Spin up application container 16 | ``` 17 | docker compose up -d 18 | ``` 19 | 20 | 4. Change directory to /kafka-cluster 21 | ``` 22 | cd kafka-cluster 23 | ``` 24 | 25 | 5. (First time running the application) Build kafka cluster image 26 | ``` 27 | docker build -t dockerpromkafka:latest . 28 | ``` 29 | 30 | 6. Spin up demo kafka-cluster container (demo Kafka-cluster container must be spun up after application container) 31 | ``` 32 | docker compose up -d 33 | ``` 34 | 35 | 7. Run the consumer followed by producer script 36 | ``` 37 | node consumer.js 38 | node producer.js 39 | ``` 40 | 41 | 8. Log into Grafana account at locahost:3002 42 | - Sign in with credentials admin/kafkakarepw 43 | 44 | 9. Visit application frontend at localhost:3000 45 | - Enjoy 46 | 47 | 48 | ## Instructions to stop the application 49 | 1. Spin down application container 50 | ``` 51 | docker compose down 52 | ``` 53 | 54 | 2. Change directory to /kafka-cluster 55 | ``` 56 | cd kafka-cluster 57 | ``` 58 | 59 | 3. Spin down demo kafka-cluster container 60 | ``` 61 | docker compose down 62 | ``` 63 | 64 | ## Instructions to connect Slack Notifications 65 | - For detailed instructions on how to set up and connect Slack notifications, please refer to our [Slack Setup Guide](./docs/SLACK_SETUP_GUIDE.md). -------------------------------------------------------------------------------- /src/app/api/auth/[...nextauth]/route.js: -------------------------------------------------------------------------------- 1 | import NextAuth from 'next-auth'; 2 | import GoogleProvider from 'next-auth/providers/google'; 3 | import GithubProvider from 'next-auth/providers/github'; 4 | 5 | const handler = NextAuth({ 6 | providers: [ 7 | GoogleProvider({ 8 | clientId: process.env.AUTH_GOOGLE_ID, 9 | clientSecret: process.env.AUTH_GOOGLE_SECRET, 10 | 11 | //Let user choose new google account after logging out 12 | authorization: { 13 | params: { 14 | prompt: "consent", 15 | access_type: "offline", 16 | response_type: "code" 17 | } 18 | } 19 | }), 20 | GithubProvider({ 21 | clientId: process.env.AUTH_GITHUB_ID, 22 | clientSecret: process.env.AUTH_GITHUB_SECRET, 23 | 24 | //Let user choose new google account after logging out 25 | authorization: { 26 | params: { 27 | prompt: "consent", 28 | access_type: "offline", 29 | response_type: "code" 30 | } 31 | } 32 | }), 33 | ], 34 | callbacks: { 35 | async jwt({ token, account, profile }) { 36 | // Persist the OAuth access_token and or the user id to the token right after signin 37 | if (account) { 38 | token.provider = account.provider; 39 | } 40 | return token; 41 | }, 42 | async session({ session, token, user }) { 43 | // Send properties to the client, like an access_token and user id from a provider. 44 | session.user.provider = token.provider; 45 | 46 | return session; 47 | } 48 | }, 49 | session: {maxAge: 60 * 60}, 50 | secret: process.env.NEXTAUTH_SECRET 51 | }); 52 | export { handler as GET, handler as POST }; -------------------------------------------------------------------------------- /server/controllers/testingController.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/userModel.js"); 2 | const Cluster = require("../models/clusterModel.js"); 3 | 4 | const testingController = {}; 5 | 6 | /* ------------------------ TESTING // GET ALL USERS ------------------------ */ 7 | testingController.getAllUsers = async (req, res, next) => { 8 | console.log("In testingController.getAllUsers"); // testing 9 | 10 | try { 11 | const users = await User.find({}); 12 | if (users.length === 0) { 13 | return res.status(200).json({ message: "No users have signed up" }); 14 | } else if (users.length > 0) { 15 | res.locals.allUsers = users; 16 | return next(); 17 | } 18 | } catch (err) { 19 | return next({ 20 | log: `testingController.getAllUsers: ERROR ${err}`, 21 | status: 500, 22 | message: { err: "Error occurred in testingController.getAllUsers." }, 23 | }); 24 | } 25 | }; 26 | 27 | 28 | /* ----------------------- TESTING // GET ALL CLUSTERS ---------------------- */ 29 | testingController.getAllClusters = async (req, res, next) => { 30 | console.log("In testingController.getAllClusters"); // testing 31 | 32 | try { 33 | const clusters = await Cluster.find({}); 34 | if (clusters.length === 0) { 35 | return res.status(200).json({ message: "No clusters have been created" }); 36 | } else if (clusters.length > 0) { 37 | res.locals.allClusters = clusters; 38 | return next(); 39 | } 40 | } catch (err) { 41 | return next({ 42 | log: `testingController.getAllClusters: ERROR ${err}`, 43 | status: 500, 44 | message: { err: "Error occurred in testingController.getAllClusters." }, 45 | }); 46 | } 47 | }; 48 | 49 | // Export 50 | module.exports = testingController; 51 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | 4 | const config = { 5 | mode: process.env.NODE_ENV, 6 | entry: "./client/src/index.jsx", 7 | output: { 8 | filename: "bundle.js", 9 | path: path.resolve(__dirname, "client", "dist"), 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx?$/, 15 | use: { 16 | loader: "babel-loader", 17 | options: { 18 | presets: ["@babel/preset-env", "@babel/preset-react"], 19 | }, 20 | }, 21 | exclude: /node_modules/, 22 | }, 23 | { 24 | test: /\.s?css$/, 25 | use: ["style-loader", "css-loader", "sass-loader"], 26 | }, 27 | { 28 | test: /\.(svg|jpg|jpeg|gif|webp)$/i, 29 | type: 'asset/resource', 30 | }, 31 | { 32 | test: /\.png$/, 33 | use: [ 34 | { 35 | loader: "url-loader", 36 | options: { 37 | mimetype: "image/png", 38 | }, 39 | }, 40 | ], 41 | }, 42 | ], 43 | }, 44 | plugins: [ 45 | new HtmlWebpackPlugin({ 46 | template: "client/public/index.html", 47 | filename: "index.html", 48 | }), 49 | ], 50 | devServer: { 51 | host: 'localhost', 52 | port: 8080, 53 | static: { 54 | directory: path.join(__dirname, 'client'), 55 | publicPath: '/', 56 | }, 57 | hot: true, // reload without a refresh 58 | historyApiFallback: true, 59 | headers: { 'Access-Control-Allow-Origin': '*' }, 60 | proxy: { 61 | '/': { 62 | target: 'http://localhost:3000/', 63 | secure: false, 64 | }, 65 | }, 66 | }, 67 | }; 68 | 69 | module.exports = config; -------------------------------------------------------------------------------- /grafana/datasource.yml: -------------------------------------------------------------------------------- 1 | # config file version 2 | apiVersion: 1 3 | 4 | # list of datasources that should be deleted from the database 5 | deleteDatasources: 6 | - name: Prometheus 7 | orgId: 1 8 | 9 | # list of datasources to insert/update depending 10 | # what's available in the database 11 | datasources: 12 | # name of the datasource. Required 13 | - name: Prometheus 14 | # datasource type. Required 15 | type: prometheus 16 | # access mode. proxy or direct (Server or Browser in the UI). Required 17 | access: proxy 18 | # org id. will default to orgId 1 if not specified 19 | orgId: 1 20 | # url 21 | url: http://prometheus:9090 22 | # database password, if used 23 | password: 24 | # database user, if used 25 | user: 26 | # database name, if used 27 | database: 28 | # enable/disable basic auth 29 | basicAuth: false 30 | # basic auth username 31 | # basicAuthUser: admin 32 | # basic auth password 33 | # basicAuthPassword: kafka 34 | # enable/disable with credentials headers 35 | withCredentials: true 36 | # mark as default datasource. Max one per org 37 | isDefault: true 38 | # fields that will be converted to json and stored in json_data 39 | # jsonData: 40 | # graphiteVersion: '1.1' 41 | # tlsAuth: false 42 | # tlsAuthWithCACert: false 43 | # # json object of data that will be encrypted. 44 | # secureJsonData: 45 | # tlsCACert: '...' 46 | # tlsClientCert: '...' 47 | # tlsClientKey: '...' 48 | # version: 1 49 | # # allow users to edit datasources from the UI. 50 | # editable: true -------------------------------------------------------------------------------- /server/models/userModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | const bcrypt = require("bcryptjs"); 4 | 5 | const SALT_WORK_FACTOR = 10; 6 | 7 | const userSchema = new Schema({ 8 | // Required 9 | username: { 10 | type: String, 11 | required: true, 12 | }, 13 | password: { 14 | type: String, 15 | required: false 16 | }, 17 | createdAt: { 18 | type: Date, 19 | default: Date.now 20 | }, 21 | lastVisited: { 22 | type: Date, 23 | default: Date.now 24 | }, 25 | firstName: { 26 | type: String 27 | }, 28 | lastName: { 29 | type: String 30 | }, 31 | birthday: { 32 | type: Date 33 | }, 34 | email: { 35 | type: String 36 | }, 37 | slackUrl: { 38 | type: String, 39 | default: '' 40 | }, 41 | promUrl: { 42 | type: String, 43 | default: '' 44 | }, 45 | settings: { 46 | colorMode: { 47 | type: String, 48 | default: 'light' 49 | }, 50 | language: { 51 | type: String, 52 | default: 'English' 53 | } 54 | }, 55 | oAuthProvider: { 56 | type: String, 57 | default: 'none' 58 | }, 59 | graphs: { 60 | type: [{}], 61 | default: [] 62 | }, 63 | }); 64 | userSchema.index({ username: 1, email: 1, oAuthProvider: 1 }, { unique: true }); 65 | 66 | // Pre-save hook to encrypt password using bcrypt.hash() 67 | userSchema.pre("save", async function (next) { 68 | try { 69 | // Only hash the password if it has been modified (or is new) 70 | if (!this.isModified('password')) return next(); 71 | 72 | const salt = await bcrypt.genSalt(SALT_WORK_FACTOR); 73 | this.password = await bcrypt.hash(this.password, salt); 74 | return next(); 75 | } catch (err) { 76 | return next(err); 77 | } 78 | }); 79 | 80 | const User = mongoose.model("User", userSchema); 81 | 82 | module.exports = User; 83 | -------------------------------------------------------------------------------- /src/components/alerts/configureCustom.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Box, 4 | Button, 5 | Heading, 6 | Input, 7 | Text, 8 | } from "@chakra-ui/react"; 9 | 10 | const ConfigureCustom = ({ 11 | allMetrics, 12 | minThresholds, 13 | setMinThresholds, 14 | maxThresholds, 15 | setMaxThresholds, 16 | removeAlert, 17 | saveAlerts, 18 | }) => { 19 | 20 | return ( 21 | 22 | 23 | Configure Custom Alerts 24 | 25 | {Object.keys(allMetrics).map((metric, index) => ( 26 | 27 | {metric} 28 | { 33 | const updatedThresholds = [...minThresholds]; 34 | updatedThresholds[index] = e.target.value; 35 | setMinThresholds(updatedThresholds); 36 | }} 37 | /> 38 | { 43 | const updatedThresholds = [...maxThresholds]; 44 | updatedThresholds[index] = e.target.value; 45 | setMaxThresholds(updatedThresholds); 46 | }} 47 | /> 48 | 56 | 57 | ))} 58 | 59 | 60 | ); 61 | }; 62 | 63 | export default ConfigureCustom; 64 | 65 | -------------------------------------------------------------------------------- /src/components/alerts/alertsByMetricGraph.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | 4 | const AlertsByMetricGraph = ({ allMetrics }) => { 5 | const chartRef = useRef(null); 6 | 7 | useEffect(() => { 8 | if (allMetrics && chartRef.current) { 9 | const ctx = chartRef.current.getContext('2d'); 10 | 11 | // Destroy existing chart instance if it exists 12 | if (chartRef.current.chart) { 13 | chartRef.current.chart.destroy(); 14 | } 15 | 16 | // Create datasets from allMetrics object with initial values of 0 and random colors and border 17 | const datasets = Object.keys(allMetrics).map((metric, index) => ({ 18 | label: metric, 19 | data: Array(Object.keys(allMetrics).length).fill(0), 20 | backgroundColor: `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.2)`, 21 | borderColor: `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 1)`, 22 | borderWidth: 1 23 | })); 24 | 25 | // Create new chart instance 26 | chartRef.current.chart = new Chart(ctx, { 27 | type: 'bar', 28 | data: { 29 | labels: Object.keys(allMetrics), 30 | datasets: datasets 31 | }, 32 | options: { 33 | indexAxis: 'y', 34 | scales: { 35 | x: { 36 | beginAtZero: true 37 | }, 38 | y: { 39 | stacked: true 40 | } 41 | }, 42 | plugins: { 43 | title: { 44 | display: true, 45 | text: 'All Time Number of Alerts by Metric' 46 | } 47 | } 48 | } 49 | }); 50 | } 51 | }, [allMetrics]); 52 | 53 | 54 | return ; 55 | }; 56 | 57 | export default AlertsByMetricGraph; 58 | 59 | 60 | -------------------------------------------------------------------------------- /server/routes/authRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const userController = require("../controllers/userController"); 4 | const tokenController = require("../controllers/tokenController"); 5 | 6 | // Route for user signup 7 | router.post( 8 | '/signup', 9 | userController.createUser, 10 | tokenController.issueToken, 11 | (req, res) => { 12 | return res.status(201).json({ message: `User registered successfully: ${res.locals.username}` }); 13 | }); 14 | 15 | // Route for user login 16 | router.post( 17 | '/login', 18 | userController.verifyUser, 19 | tokenController.issueToken, 20 | (req, res) => { 21 | return res.status(200).json({ message: `User logged in successfully: ${res.locals.username}` }); 22 | }); 23 | 24 | // Route for user logout 25 | router.get( 26 | '/logout', 27 | tokenController.verifyToken, 28 | userController.logout, 29 | (req, res) => { 30 | return res.status(201).json({ message: `User logged out successfully: ${res.locals.username}` }); 31 | }); 32 | 33 | // Route for changing user password 34 | router.patch( 35 | '/password/update', 36 | tokenController.verifyToken, 37 | userController.updatePassword, 38 | (req, res) => { 39 | return res.status(200).json({ message: 'User updated password successfully' }); 40 | } 41 | ) 42 | 43 | // Route for deleting a user account 44 | router.delete( 45 | '/account/delete', 46 | tokenController.verifyToken, 47 | userController.deleteAccount, 48 | (req, res) => { 49 | return res.status(200).json({ message: 'User account deleted successfully '}); 50 | } 51 | ) 52 | 53 | //Route for checking is user is new 54 | // router.get( 55 | // '/check-new-user', 56 | // tokenController.verifyToken, 57 | // userController.checkNewUsers, 58 | // (req, res) => { 59 | // return res.status(200).json({ isNewUser }) 60 | // } 61 | // ) 62 | 63 | module.exports = router; -------------------------------------------------------------------------------- /src/components/clusters/mainContainer/deleteClusterModal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Button, Modal, ModalOverlay, ModalContent, ModalHeader, ModalFooter, ModalBody, ModalCloseButton, useToast, 4 | Alert, AlertIcon, AlertTitle, AlertDescription, 5 | } from '@chakra-ui/react'; 6 | import { clustersStore } from '../../../store/clusters'; 7 | import { handleDeleteCluster } from '../../../utils/clustersHandler'; 8 | 9 | const DeleteClusterModal = () => { 10 | 11 | // declare state variables 12 | const oldClusterName = clustersStore((state) => state.oldClusterName); 13 | const deleteClusterID = clustersStore(state => state.deleteClusterID); 14 | const isDeleteClusterOpen = clustersStore(state => state.isDeleteClusterOpen); 15 | 16 | // declare variable to use toast 17 | const toast = useToast(); 18 | 19 | return ( 20 | 21 | /* Delete Cluster Modal */ 22 | clustersStore.setState({isDeleteClusterOpen: false})} motionPreset='slideInBottom'> 23 | 24 | 25 | 26 | {/* Title */} 27 | Delete Cluster: {oldClusterName} 28 | 29 | 30 | {/* Warning */} 31 | 32 | 33 | 34 | Are you sure? You can't undo this action afterwards. 35 | 36 | 37 | 38 | 39 | {/* Cancel Button */} 40 | 41 | 42 | {/* Delete Button */} 43 | 44 | 45 | 46 | 47 | ); 48 | }; 49 | 50 | export default DeleteClusterModal; -------------------------------------------------------------------------------- /src/app/clusters/page.jsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { useEffect } from 'react'; 4 | import { Box, useToast, useColorMode } from '@chakra-ui/react'; 5 | import { useRouter } from 'next/navigation'; 6 | import { clustersStore } from '../../store/clusters'; 7 | import Navbar from '../../components/clusters/navbar'; 8 | import MainContainer from '../../components/clusters/mainContainer'; 9 | import LoadingModal from '../../components/loadingModal'; 10 | import { handleFetchClustersAndSlackWebhookURL } from '../../utils/clustersHandler'; 11 | /* 12 | Cluster Page Structure: 13 | |-- clusters.jsx 14 | |-- navbar 15 | |-- searchInput 16 | |-- addClusterModal 17 | |-- accountMenu 18 | |-- changePasswordModal 19 | |-- deleteAccountModal 20 | |-- logoutModal 21 | |-- menuDrawer 22 | |-- mainContainer 23 | |-- clusterCard 24 | |-- deleteClusterModal 25 | |-- editClusterModal 26 | */ 27 | export default function Home() { 28 | 29 | // declare variable to use toast and push 30 | const { push } = useRouter(); 31 | const toast = useToast(); 32 | const { colorMode, toggleColorMode } = useColorMode(); 33 | 34 | // declare state variables 35 | const renderClustersPage = clustersStore(state => state.renderClustersPage); 36 | 37 | // fetch clusters and slack webhook url before rendering page 38 | useEffect(() => { 39 | setTimeout(() => {clustersStore.setState({isLoadingModalOpen: false});}, 1500); 40 | clustersStore.setState({isLoadingModalOpen: true}); 41 | console.log(colorMode); 42 | handleFetchClustersAndSlackWebhookURL(toast, push, colorMode, toggleColorMode); 43 | }, []); 44 | 45 | if (renderClustersPage) { 46 | return ( 47 | 48 | 49 | 50 | {/* Navbar */} 51 | 52 | 53 | {/* Main Container */} 54 | 55 | 56 | ); 57 | } 58 | } -------------------------------------------------------------------------------- /src/components/clusters/navbar/accountMenu/logoutModal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Button, Modal, ModalOverlay, ModalContent, ModalHeader, ModalFooter, ModalBody, ModalCloseButton, useToast, useColorModeValue 4 | } from '@chakra-ui/react'; 5 | import { useRouter } from 'next/navigation'; 6 | import { useSession, signOut } from 'next-auth/react'; 7 | import { clustersStore } from '../../../../store/clusters'; 8 | import { handleLogout } from '../../../../utils/clustersHandler'; 9 | 10 | const LogoutModal = () => { 11 | 12 | // states for properties in different color mode 13 | const deleteButtonColor = useColorModeValue('blackAlpha', 'facebook'); 14 | 15 | // declare state variables 16 | const isLogoutModalOpen = clustersStore(state => state.isLogoutModalOpen); 17 | const username = clustersStore(state => state.username); 18 | 19 | // declare variable to use toast, push, and status 20 | const { push } = useRouter(); 21 | const toast = useToast(); 22 | const { data: session } = useSession(); 23 | 24 | return ( 25 | 26 | /* Delete Cluster Modal */ 27 | clustersStore.setState({isLogoutModalOpen: false})} motionPreset='slideInBottom'> 28 | 29 | 30 | Logout 31 | 32 | 33 | {username}, are you sure you want to log out? 34 | 35 | 36 | 37 | {/* Cancel Button */} 38 | 39 | 40 | {/* Delete Button */} 41 | 45 | 46 | 47 | 48 | ); 49 | }; 50 | 51 | export default LogoutModal; -------------------------------------------------------------------------------- /src/app/cluster-health/page.jsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React, { useState, useEffect } from 'react'; 4 | import Navbar from '../../components/index/navbar'; 5 | import { Heading, Box, Grid, GridItem } from '@chakra-ui/react'; 6 | 7 | const ClusterHealth = () => { 8 | const [windowWidth, setWindowWidth] = useState(0); 9 | 10 | // update the window width 11 | const handleResize = () => { 12 | setWindowWidth(window.innerWidth); 13 | }; 14 | 15 | // useEffect to run the handleResize function on window resize 16 | useEffect(() => { 17 | window.addEventListener('resize', handleResize); 18 | handleResize(); 19 | return () => window.removeEventListener('resize', handleResize); 20 | }, []); 21 | 22 | const clusterHealthMetrics = [ 23 | { title: 'Broker Disk Usage', panelId: 15 }, 24 | { title: 'JVM Memory Pool Bytes', panelId: 8 }, 25 | { title: 'Offline Partitions Count', panelId: 10 }, 26 | { title: 'Under Replicated Partitions Count', panelId: 11 }, 27 | { title: 'Active Controller Count', panelId: 9 }, 28 | { title: 'Total Failed Fetch Requests', panelId: 4 }, 29 | ]; 30 | 31 | const renderMetric = (metric) => ( 32 | 33 | )} 130 | 139 | {selectedMetricId.map((metric, index) => ( 140 | handleRemoveMetric(metric)} 144 | showFullDashboard={showFullDashboard} 145 | iFrameUrl={iFrameUrl} 146 | />))} 147 | 148 | 149 | ); 150 | }; 151 | 152 | export default Dashboard; 153 | 154 | 155 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | // Access to environmental variables 2 | require("dotenv").config(); 3 | 4 | // Import dependencies 5 | const express = require("express"); 6 | const next = require("next"); 7 | const path = require("path"); 8 | const mongoose = require("mongoose"); 9 | const cors = require("cors"); 10 | const cookieParser = require("cookie-parser"); 11 | const axios = require('axios'); 12 | 13 | // Import routes 14 | const authRoutes = require("./routes/authRoutes"); 15 | const clustersRoutes = require("./routes/clustersRoutes"); 16 | const metricsRoutes = require("./routes/metricsRoutes"); 17 | const testingRoutes = require("./routes/testingRoutes"); 18 | const slackRoutes = require("./routes/slackRoutes"); 19 | const settingsRoutes = require("./routes/settingsRoutes"); 20 | const oAuthRoutes = require("./routes/oAuthRoutes"); 21 | const grafanaApiRoutes = require("./routes/grafanaApiRoutes"); 22 | const iFrameRoutes = require("./routes/iFrameRoutes"); 23 | 24 | // Setup Next app 25 | const PORT = 3001; 26 | const dev = process.env.NODE_ENV !== "production"; // dev = true if node_env IS NOT production 27 | const app = next({ dev }); // initializes an instance of a NextJS app 28 | const handle = app.getRequestHandler(); // handles page routing 29 | 30 | // Prepare to serve the NextJS app 31 | app.prepare().then(() => { 32 | const server = express(); 33 | 34 | // CORS middleware 35 | const corsOptions = { 36 | origin: "http://localhost:3000", 37 | credentials: true, 38 | }; 39 | server.use(cors(corsOptions)); 40 | 41 | // Middleware 42 | server.use(cookieParser()); 43 | server.use(express.json()); 44 | server.use(express.urlencoded({ extended: true })); 45 | 46 | // Connect to mongoDB 47 | const mongoURI = `mongodb://admin:supersecret@mongo`; 48 | const mongoURIAtlas = process.env.MONGODB_URI; 49 | 50 | mongoose.connect(mongoURI); 51 | mongoose.connection.once("open", () => { 52 | console.log("Connected to Database"); 53 | }); 54 | // options for mongoose.connect 55 | // {useNewUrlParser: true, 56 | // useUnifiedTopology: true, 57 | // serverSelectionTimeoutMS: 5000 // Timeout after 5s instead of 10s 58 | // } 59 | 60 | //================== TEST 61 | 62 | // Test MongoDB connection route 63 | server.get('/test-db', async (req, res) => { 64 | try { 65 | await mongoose.connect(mongoURI, { useNewUrlParser: true, useUnifiedTopology: true }); 66 | const connectionState = mongoose.connection.readyState; 67 | if (connectionState === 1) { 68 | res.status(200).json({ message: 'Successfully connected to MongoDB' }); 69 | } else { 70 | res.status(500).json({ message: 'Failed to connect to MongoDB', connectionState }); 71 | } 72 | } catch (error) { 73 | res.status(500).json({ message: 'Error connecting to MongoDB', error: error.message }); 74 | } 75 | }); 76 | 77 | 78 | //=========================== TEST 79 | 80 | //=========================== TEST 81 | server.get('/api/get-datasources', async (req, res) => { 82 | try { 83 | const response = await axios.get('http://grafana:3000/api/datasources', { 84 | headers: { 85 | 'Authorization': `Bearer ${process.env.GRAFANA_SERVICE_ACCOUNT_TOKEN}` 86 | } 87 | }); 88 | 89 | res.status(200).json(response.data); 90 | } catch (error) { 91 | console.error('Failed to get datasources:', error.response.data); 92 | res.status(500).json({ message: error.message }); 93 | } 94 | }); 95 | 96 | //================================================================================================ 97 | // this is the code to update the datasource file in the backend server so it won't be overwritten 98 | // const fs = require('fs'); 99 | // const yaml = require('js-yaml'); 100 | 101 | // server.put('/api/update-datasource-file/:id', (req, res) => { 102 | // try { 103 | // // Load the datasource configuration from the yml file 104 | // const fileContents = fs.readFileSync('/path/to/your/datasource.yml', 'utf8'); 105 | // const data = yaml.safeLoad(fileContents); 106 | 107 | // // Update the port in the datasource configuration 108 | // data.datasources[0].url = `http://prometheus:${newPort}/api/v1/query_range`; 109 | 110 | // // Write the updated configuration back to the yml file 111 | // const newYaml = yaml.safeDump(data); 112 | // fs.writeFileSync('/path/to/your/datasource.yml', newYaml, 'utf8'); 113 | 114 | // // Return a success response 115 | // res.status(200).json({ message: 'Datasource file updated successfully' }); 116 | // } catch (error) { 117 | // // Return an error response 118 | // console.error('Failed to update datasource file:', error); 119 | // res.status(500).json({ message: error.message }); 120 | // } 121 | // }); 122 | 123 | 124 | //============================= 125 | // Custom routes 126 | server.use("/auth", authRoutes); 127 | server.use("/clusters", clustersRoutes); 128 | server.use("/metrics", metricsRoutes); 129 | server.use("/testing", testingRoutes); // testing 130 | server.use("/slack", slackRoutes); 131 | server.use("/settings", settingsRoutes); 132 | server.use("/oauth", oAuthRoutes); 133 | server.use("/api", grafanaApiRoutes); 134 | server.use("/iframe", iFrameRoutes); 135 | 136 | // Fallback route 137 | server.get("*", (req, res) => { 138 | return handle(req, res); 139 | }); 140 | 141 | // Express global error handler 142 | server.use((err, req, res, next) => { 143 | const defaultObj = { 144 | log: "Express error handler caught unknown middleware error", 145 | status: 500, 146 | message: { err: "An error occurred" }, 147 | }; 148 | const errObj = Object.assign({}, defaultObj, err); 149 | console.log(errObj.log); 150 | return res.status(errObj.status).json(errObj.message); 151 | }); 152 | 153 | // Start server 154 | server.listen(PORT, () => { 155 | console.log(`🚀 Server launching on http://localhost:${PORT}`); 156 | }); 157 | }); 158 | -------------------------------------------------------------------------------- /src/components/clusters/menuDrawer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | FormLabel, Box, Link, Code, Textarea, Flex, Icon, IconButton, 4 | Drawer, DrawerBody, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerContent, DrawerCloseButton, 5 | Step, StepDescription, StepIcon, StepIndicator, StepNumber, StepSeparator, StepStatus, StepTitle, Stepper 6 | } from '@chakra-ui/react'; 7 | import { RiSendPlane2Fill } from 'react-icons/ri'; 8 | import { clustersStore } from '../../store/clusters'; 9 | 10 | const MenuDrawer = ({ handleSlackWebhookURLSubmit }) => { 11 | 12 | // declare state variables 13 | const isDrawerOpen = clustersStore(state => state.isDrawerOpen); 14 | const slackWebhookURL = clustersStore(state => state.slackWebhookURL); 15 | 16 | // declare reference modal initial focus 17 | const initialRef = React.useRef(null); 18 | 19 | // check slack url format before submit 20 | const submitSlackWebhookUrl = () => { 21 | if (slackWebhookURL.slice(0, 34) === 'https://hooks.slack.com/services/T' 22 | && slackWebhookURL.indexOf('/B') - slackWebhookURL.indexOf('/T') >= 10 23 | && slackWebhookURL.lastIndexOf('/') - slackWebhookURL.indexOf('/B') >= 9 24 | && slackWebhookURL.length >= 77 25 | ) { 26 | alert('Right Format'); 27 | handleSlackWebhookURLSubmit(); 28 | } else { 29 | alert('Wrong Format'); 30 | } 31 | } 32 | 33 | // declare slack webhook step array 34 | // { title: (String), description: (Function that returns JSX)} 35 | const slackWebhookSteps = [ 36 | { title: 'First', description: () => ( 37 | <> 38 | Go to 39 | Create your Slack App 40 | and pick a name, choose a workspace to associate your app with, then click Create App button. 41 | 42 | )}, 43 | { title: 'Second', description: () => ( 44 | <> 45 | You should be redirect to 46 | App Management Dashboard 47 | . From here, select Incoming Webhooks at the left under Features, and toggle Activate Incoming Webhooks to on. 48 | 49 | )}, 50 | { title: 'Third', description: () => ( 51 | <> 52 | Now that incoming webhooks are enabled, the settings page should refresh and some additional options will appear. Click the Add New Webhook to Workspace button at the bottom. 53 | 54 | )}, 55 | { title: 'Fourth', description: () => ( 56 | <> 57 | Pick a channel that the app will post to, then select Authorize. If you need to add the incoming webhook to a private channel, you must first be in that channel. You'll be sent back to  58 | 59 | App Management Dashboard 60 | , where you should see your webhook URL, which will look something like this: 61 | https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX 62 | 63 | )}, 64 | ]; 65 | 66 | return ( 67 | 68 | /* Menu Drawer */ 69 | clustersStore.setState({isDrawerOpen: false})} isOpen={isDrawerOpen} initialFocusRef={initialRef}> 70 | 71 | 72 | 73 | 74 | {/* Title */} 75 | Menu 76 | 77 | {/* Content */} 78 | 79 | 80 | {/* URL Subtitle */} 81 | Slack Webhook URL 82 | 83 | 84 | 85 | {/* URL Input */} 86 |