Go to router.push(redirectURL)} className='font-bold focus:outline-none text-blue-700 hover:text-primary-600'>Connect Cluster page to add additional members
+
+
+ )
+
+};
+
+
+
+
+ export default Table;
\ No newline at end of file
diff --git a/app/_components/homePage/clusterDetails.tsx b/app/_components/homePage/clusterDetails.tsx
new file mode 100644
index 0000000..d143692
--- /dev/null
+++ b/app/_components/homePage/clusterDetails.tsx
@@ -0,0 +1,13 @@
+import { Divider } from "@tremor/react";
+
+export default async function clusterDetails({ cluster_name, cluster_ip }: { cluster_name: string, cluster_ip: string }) {
+
+ return (
+
+ )
+ }
+}
+
diff --git a/app/_components/homePage/cpuMemCircle 2.tsx b/app/_components/homePage/cpuMemCircle 2.tsx
new file mode 100644
index 0000000..c533e01
--- /dev/null
+++ b/app/_components/homePage/cpuMemCircle 2.tsx
@@ -0,0 +1,40 @@
+'use client'
+
+import React from 'react'
+import { ProgressCircle, Card } from "@tremor/react";
+import { clusterMemoryUsage, clusterCpuUsage10mAvg } from '../../lib/queries';
+
+export default function CPUMemCircle({ cpu, memory }: { cpu: number, memory: number }) {
+
+ return (
+
+
+
+
Cluster Memory Usage
+
+ {memory.toFixed(2)}%
+
+
+
+
+
Cluster CPU Usage
+
+ {cpu.toFixed(2)}%
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/app/_components/homePage/cpuMemCircle.tsx b/app/_components/homePage/cpuMemCircle.tsx
new file mode 100644
index 0000000..fb83821
--- /dev/null
+++ b/app/_components/homePage/cpuMemCircle.tsx
@@ -0,0 +1,40 @@
+'use client'
+
+import React from 'react'
+import { ProgressCircle, Card } from "@tremor/react";
+import { clusterMemoryUsage, clusterCpuUsage10mAvg } from '../../lib/queries';
+
+export default function CPUMemCircle({ cpu, memory }: { cpu: number, memory: number }) {
+
+ return (
+
+
+
+
Cluster Memory Usage
+
+ {memory.toFixed(2)}%
+
+
+
+
+
Cluster CPU Usage
+
+ {cpu.toFixed(2)}%
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/app/_components/homePage/homeAlerts.tsx b/app/_components/homePage/homeAlerts.tsx
new file mode 100644
index 0000000..0398124
--- /dev/null
+++ b/app/_components/homePage/homeAlerts.tsx
@@ -0,0 +1,36 @@
+import Link from 'next/link'
+import { numProgressAlerts, numTotalAlerts, numCriticalAlerts, numOpenAlerts } from '../../lib/homePage/numOfAlerts';
+
+export default async function homeAlerts({ cluster_ip }: { cluster_ip: string }) {
+ const totalAlerts = await numTotalAlerts(cluster_ip);
+ const totalOpenAlerts = await numOpenAlerts(cluster_ip);
+ const totalInProgressAlerts = await numProgressAlerts(cluster_ip);
+ const totalCriticalAlerts = await numCriticalAlerts(cluster_ip);
+
+ return (
+
+
+
+
+
Total Incidents
+
{totalAlerts}
+
+
+
Open Incidents
+
{totalOpenAlerts}
+
+
+
In Progress Incidents
+
{totalInProgressAlerts}
+
+
+
Critical Incidents
+
{totalCriticalAlerts}
+
+
+
+
+ )
+}
+
+
diff --git a/app/_components/homePage/loadingSpinner.tsx b/app/_components/homePage/loadingSpinner.tsx
new file mode 100644
index 0000000..f4c9915
--- /dev/null
+++ b/app/_components/homePage/loadingSpinner.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+
+export default function LoadingSpinner() {
+ return (
+
+
+
+
+ Loading...
+
+
+
+ )
+}
diff --git a/app/_components/homePage/nodeCircle.tsx b/app/_components/homePage/nodeCircle.tsx
new file mode 100644
index 0000000..8255b04
--- /dev/null
+++ b/app/_components/homePage/nodeCircle.tsx
@@ -0,0 +1,25 @@
+'use client'
+
+import React from 'react'
+import { ProgressCircle, Card, Flex } from "@tremor/react";
+
+export default function NodeCircle({ name, value }: { name: string, value: number }) {
+ return (
+
+
+
{name}
+
+
+ {value.toFixed(2)}%
+
+
+
+
+ )
+}
diff --git a/app/_components/incident-details/EditDetails.tsx b/app/_components/incident-details/EditDetails.tsx
new file mode 100644
index 0000000..047d982
--- /dev/null
+++ b/app/_components/incident-details/EditDetails.tsx
@@ -0,0 +1,50 @@
+import * as React from 'react';
+import { EditDetailsType } from '../../../types/definitions'
+
+
+
+const EditDetails = (props: EditDetailsType) => {
+
+ return (
+
+
+
+
+
Title:
+
{props.incident_title}
+
+
Assigned By:
+
{props.incident_assigned_by}
+
+
Due Date:
+
{props.incident_due_date}
+
+
Status:
+
{props.incident_status}
+
+
Notes:
+
{props.comment}
+
+
+
+
Priority:
+
{props.priority_level}
+
+
Assigned To:
+
{props.incident_assigned_to}
+
+
Assigned Date:
+
{props.incident_assigned_date}
+
+
Description:
+
{props.description}
+
+
+
+
+ )
+
+
+};
+
+export default EditDetails;
\ No newline at end of file
diff --git a/app/_components/incident-details/EditForm.tsx b/app/_components/incident-details/EditForm.tsx
new file mode 100644
index 0000000..6720dfc
--- /dev/null
+++ b/app/_components/incident-details/EditForm.tsx
@@ -0,0 +1,174 @@
+import * as React from 'react';
+import { FormEvent } from 'react';
+import {useRouter} from 'next/navigation';
+import { Incident, UserName } from '../../../types/definitions'
+
+
+const EditForm = (props: {
+ incident_title?: string,
+ priority_level?: string,
+ incident_status?: string,
+ description?: string,
+ comment?: string,
+ incident_assigned_to?: string,
+ incident_assigned_by?: string,
+ incident_assigned_date?: string,
+ incident_due_date?: string,
+ incident_type?: string,
+ cluster_name?: string,
+ incident_id?: string,
+ cluster_id?: string,
+ updateEdit?: Function,
+ incident_date?: string,
+ memberProps?: [{name: string, email: string}],
+}) => {
+
+ // create new empty array to hold members
+ let memberArray: Array = [];
+
+ // function to push members from props to member array
+ function getMembers(array: Array) {
+ for (let i = 0; i < array.length; i++) {
+ memberArray.push(array[i].name)
+ }
+ };
+
+ // invoke function to push members from props to member array
+ if (props.memberProps !== undefined) {
+ getMembers(props.memberProps);
+ }
+
+ const router = useRouter();
+
+ // function to handle submit when user saves edits
+ async function onSubmit(event: FormEvent) {
+ event.preventDefault();
+
+ const formData = new FormData(event.currentTarget);
+
+ // save old state to body object
+ let body: Incident = JSON.parse(JSON.stringify(props));
+
+ // if incident_id, cluster_id, and incident_date on props have data, assign them to body object
+ if (props.incident_id !== undefined && props.cluster_id !== undefined && props.incident_date !== undefined) {
+ body.incident_id = props.incident_id;
+ body.cluster_id = props.cluster_id;
+ body.incident_date = props.incident_date;
+ }
+
+ // declare newState object and assign it values of body (old state)
+ let newState: Incident = body;
+
+ // iterate through body object, comparing values to those of the submitted form
+ // if keys match, but values don't, assign value of form to body
+ // assign matching key:value pairs from body to newState
+ for (let key in body) {
+ if (key !== 'members' && key !== 'updateEdit') {
+ for (let pair of formData.entries()) {
+ if (key === pair[0] && body[key as keyof typeof body] !== pair[1].toString() && pair[1].toString() !== 'mm/dd/yyyy') {
+ body[key as keyof typeof body] = pair[1].toString();
+ }
+ newState[key as keyof typeof newState] = body[key as keyof typeof body]
+ }
+ }
+ }
+
+ // update state on Table component with newState values (form values from edit page)
+ if (props.updateEdit !== undefined) {
+ props.updateEdit(newState);
+ };
+
+ // send post request with new values from form
+ const response = await fetch('/api/incidents/updateDetails',{
+ method:'POST',
+ body: JSON.stringify(body),
+ })
+ let res = await response.json();
+ console.log(res);
+
+ };
+
+ // create variables to hold due date and assigned date values
+ let incident_due_date: (string | undefined) = '';
+ let incident_assigned_date: (string | undefined) = '';
+
+ // if props.incident_due_date or props.incident_assigned_date are empty, assign a placeholder value, otherwise, assign the props value from DB
+ if (props.incident_due_date === '') {
+ incident_due_date = 'mm/dd/yyyy'
+ } else {
+ incident_due_date = props.incident_due_date
+ }
+
+ if (props.incident_assigned_date === '') {
+ incident_assigned_date = 'mm/dd/yyyy'
+ } else {
+ incident_assigned_date = props.incident_assigned_date
+ }
+
+
+ return (
+
+
+
+ )
+
+};
+
+export default EditForm;
\ No newline at end of file
diff --git a/app/_components/incident-details/PermanentDetails.tsx b/app/_components/incident-details/PermanentDetails.tsx
new file mode 100644
index 0000000..eb5077f
--- /dev/null
+++ b/app/_components/incident-details/PermanentDetails.tsx
@@ -0,0 +1,25 @@
+import * as React from 'react';
+import { PermanentDetailsType } from '../../../types/definitions';
+
+
+const PermanentDetails = (props: PermanentDetailsType) => {
+
+ return (
+
+ );
+}
diff --git a/app/_components/landingPage/firstinfoSection.tsx b/app/_components/landingPage/firstinfoSection.tsx
new file mode 100644
index 0000000..a073550
--- /dev/null
+++ b/app/_components/landingPage/firstinfoSection.tsx
@@ -0,0 +1,36 @@
+import Image from "next/image";
+
+export default function FirstInfoSection() {
+ return (
+
+
+
+
+
+
+ Monitor Your Incidents Alongside Cluster Health
+
+
+ See the most critical incident metrics pulled directly from
+ Prometheus Alert Manager
+
+
+ Check your clusters health directly from the homepage ensuring
+ high availability and constant uptime
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/_components/landingPage/footerSection.tsx b/app/_components/landingPage/footerSection.tsx
new file mode 100644
index 0000000..b7b1f9a
--- /dev/null
+++ b/app/_components/landingPage/footerSection.tsx
@@ -0,0 +1,91 @@
+import Link from "next/link";
+import Image from "next/image";
+
+export default function Footer() {
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/app/_components/landingPage/mediumSection.tsx b/app/_components/landingPage/mediumSection.tsx
new file mode 100644
index 0000000..567bfb5
--- /dev/null
+++ b/app/_components/landingPage/mediumSection.tsx
@@ -0,0 +1,43 @@
+import Link from "next/link";
+
+export default function MediumArticleSection() {
+ return (
+
+
+
+
+ Managing Incidents for Modern-Day DevOps Teams
+
+
+ NotiKube is a lightweight incident management platform that utilizes
+ Prometheus Alert Manager and combines traditional incident
+ management tools like OpsGenie and ServiceNow into a centralized
+ platform
+
+
+ Read our Medium article to learn more about the problem we're
+ tackling and how we landed on the idea
+
+
+ Learn more
+
+
+
+
+
+ );
+}
diff --git a/app/_components/landingPage/navbar.tsx b/app/_components/landingPage/navbar.tsx
new file mode 100644
index 0000000..8b88535
--- /dev/null
+++ b/app/_components/landingPage/navbar.tsx
@@ -0,0 +1,41 @@
+import Link from "next/link";
+import Image from "next/image";
+
+export default function Navbar() {
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/app/_components/landingPage/secondInfoSection.tsx b/app/_components/landingPage/secondInfoSection.tsx
new file mode 100644
index 0000000..93a234b
--- /dev/null
+++ b/app/_components/landingPage/secondInfoSection.tsx
@@ -0,0 +1,32 @@
+import Image from "next/image";
+
+export default function SecondInfoSection() {
+ return (
+
+
+
+
+
+
+
+ Track Your Incidents
+
+
+ View details for each incident through our intuitive dashboard
+
+
+ Our application uses the Prometheus API to provide real-time
+ alerting and incident management. Track your most critical alerts
+ and see what priorities they are
+
+ NotiKube is a distributed team with engineers from around the world
+
+
+
+ {teamMembers.map((member, index) => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/app/_components/landingPage/titleSection.tsx b/app/_components/landingPage/titleSection.tsx
new file mode 100644
index 0000000..1b93152
--- /dev/null
+++ b/app/_components/landingPage/titleSection.tsx
@@ -0,0 +1,48 @@
+import Link from "next/link";
+import Image from "next/image";
+
+export default function TitleSection() {
+ return (
+
+
+
+
+
+ Incident Management for Kubernetes Clusters
+
+
+ Stay on top of critical alerts with NotiKube's intuitive
+ dashboard for complete incident lifecycle management.
+
+
+ Get Started
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/_components/userPreferences/DeleteAccount.tsx b/app/_components/userPreferences/DeleteAccount.tsx
new file mode 100644
index 0000000..d4363b3
--- /dev/null
+++ b/app/_components/userPreferences/DeleteAccount.tsx
@@ -0,0 +1,34 @@
+'use client'
+
+import * as React from 'react';
+import { useRouter } from 'next/navigation';
+import { signOut } from "next-auth/react";
+
+const DeleteAccount = (props: {user_id: (string | undefined)}) => {
+
+ const router = useRouter();
+
+ function handleClick() {
+ if (confirm('Deleting user account will remove all data associated with this account. This action cannot be undone. Do you wish to continue deleting your account?')) {
+ fetch('/api/updateUser/removeAccount', {
+ method: 'POST',
+ body: JSON.stringify({user_id: props.user_id})
+ })
+ .then((res) => {
+ return res.json();
+ })
+ .then((res) => console.log(res))
+ signOut();
+ }
+ }
+
+
+ return (
+
+ )
+
+
+
+}
+
+export default DeleteAccount;
\ No newline at end of file
diff --git a/app/_components/userPreferences/EmailSwitch.tsx b/app/_components/userPreferences/EmailSwitch.tsx
new file mode 100644
index 0000000..19e0bce
--- /dev/null
+++ b/app/_components/userPreferences/EmailSwitch.tsx
@@ -0,0 +1,54 @@
+'use client'
+
+import * as React from 'react';
+import Switch from '@mui/material/Switch';
+import { useState } from 'react';
+import { FormControlLabel, Typography } from '@mui/material';
+import { alpha, styled } from '@mui/material/styles';
+import { red } from '@mui/material/colors';
+
+
+
+const EmailSwitch = (props: {user_id: (string | undefined), status: (boolean | undefined)}) => {
+
+ const [checked, setChecked] = useState<(boolean | undefined)>(props.status);
+
+ const handleChange = (event: React.ChangeEvent) => {
+ setChecked(event.target.checked)
+ fetch('/api/updateUser/email', {
+ method:'POST',
+ body: JSON.stringify({user_id: props.user_id, status: checked})
+ })
+ .then((res) => {
+ return res.json()
+ })
+ .then((res) => console.log(res))
+ };
+
+ const RedSwitch = styled(Switch)(({ theme }) => ({
+ '& .MuiSwitch-switchBase.Mui-checked': {
+ color: red[600],
+ '&:hover': {
+ backgroundColor: alpha(red[600], theme.palette.action.hoverOpacity),
+ },
+ },
+ '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': {
+ backgroundColor: red[600],
+ },
+ }));
+
+
+
+ return (
+ }
+ label={Email Notifications}
+ />
+ )
+}
+
+export default EmailSwitch;
\ No newline at end of file
diff --git a/app/api/addMember/[urlParams]/route.ts b/app/api/addMember/[urlParams]/route.ts
new file mode 100644
index 0000000..a3f599b
--- /dev/null
+++ b/app/api/addMember/[urlParams]/route.ts
@@ -0,0 +1,45 @@
+import { NextRequest, NextResponse } from "next/server";
+import sql from '../../../utils/db';
+import { User, Incident } from '../../../../types/definitions';
+
+const CryptoJS = require('crypto-js');
+
+
+export async function GET(request: NextRequest, {params}: {params: {urlParams: any}}) {
+
+ let urlStart = '';
+ process.env.NODE_ENV === 'development' ? urlStart = 'http://localhost:3000' : urlStart = 'http://www.notikube.com'
+
+ let { urlParams } = params;
+ let cipherText: string = urlParams.replaceAll('notikube', '/')
+ let bytes = CryptoJS.AES.decrypt(cipherText, process.env.CIPHER_KEY);
+ let decrypt = bytes.toString(CryptoJS.enc.Utf8);
+
+ let idValues: Array = decrypt.split('$')
+
+ try {
+
+ const user: User[] = await sql `
+ select * from users where email=${idValues[1]};
+ `
+
+ if (user[0] !== undefined) {
+ await sql `
+ update users set cluster_id=${idValues[0]}, cluster_owner=FALSE where email=${idValues[1]};
+ `
+ return NextResponse.redirect(urlStart + '/auth/login');
+ } else {
+ const redirectURL = urlStart + '/auth/signup'
+ return NextResponse.json({
+ Error: `Cannot find NotiKube user account for ${idValues[1]}. Please register a NotiKube user account at ${redirectURL} and try again.`
+ });
+ };
+ } catch(err) {
+ const redirectURL = urlStart + '/auth/login'
+ console.log('error', err)
+ return NextResponse.json({
+ message: `Error adding user to cluster. Please ensure you have a valid user account and log in here: ${redirectURL}`
+ });
+ }
+
+};
\ No newline at end of file
diff --git a/app/api/addMember/send-invite/route.ts b/app/api/addMember/send-invite/route.ts
new file mode 100644
index 0000000..efc8956
--- /dev/null
+++ b/app/api/addMember/send-invite/route.ts
@@ -0,0 +1,61 @@
+import { NextRequest, NextResponse } from "next/server";
+import sql from '../../../utils/db';
+import { User } from '../../../../types/definitions';
+import { sendMail } from '../../../../service/mailService';
+import { getServerSession } from "next-auth";
+
+
+const CryptoJS = require('crypto-js');
+
+
+export async function POST(request: NextRequest, response: NextResponse) {
+
+ const data: {email: string} = await request.json();
+
+ const session = await getServerSession();
+ const userEmail: (string | undefined | null) = session?.user.email;
+
+ try {
+
+ if (userEmail !== undefined) {
+
+ const user: User[] = await sql`
+ select name, cluster_id, cluster_name from users join clusters using (cluster_id) where email=${userEmail}
+`
+ const urlParams: string = user[0].cluster_id + '$' + data.email;
+ let cipherText: any = CryptoJS.AES.encrypt(urlParams, process.env.CIPHER_KEY).toString();
+ const encodedURL: string = cipherText.replaceAll('/', 'notikube');
+
+ let urlStart = '';
+ process.env.NODE_ENV === 'development' ? urlStart = 'http://localhost:3000' : urlStart = 'http://www.notikube.com'
+ let redirectURL = urlStart + '/auth/signup';
+ let addUserURL = urlStart + `/api/addMember/${encodedURL}`
+
+
+ if (data.email) {
+ console.log('sending email to: ', data.email)
+ sendMail(
+ data.email,
+ `NotiKube: Invitation to Join ${user[0].name}\'s Cluster`,
+ `You have been invited to join ${user[0].name}\'s NotiKube team: ${user[0].cluster_name}.
+
+ If you already have a NotiKube user account, click here to connect to ${user[0].cluster_name}.
+
+ Important Note: NotiKube users can only be associated with one cluster. If you are already associated with a NotiKube cluster, clicking the link above will sever your ability to view and manage incidents for your current cluster. Only click the confirmation link if you wish to change your NotiKube cluster permissions to view and manage ${user[0].cluster_name}.
+
+ If you\'re not already a registered user, click here to create a NotiKube account, then click the link above to connect to ${user[0].cluster_name}.`
+
+ )
+ }
+ }
+
+ return NextResponse.json({message: 'successfullly invited user'});
+
+} catch(err) {
+ console.log('error', err)
+ return NextResponse.json({
+ message: `Error inviting new user to cluster. Please ensure you entered a valid email address and try again.`
+ });
+}
+
+};
diff --git a/app/api/alertmanager/route 2.ts b/app/api/alertmanager/route 2.ts
new file mode 100644
index 0000000..61b1c44
--- /dev/null
+++ b/app/api/alertmanager/route 2.ts
@@ -0,0 +1,56 @@
+import { NextRequest, NextResponse } from "next/server";
+import sql from "../../utils/db";
+import { Incident } from "../../../types/definitions";
+import { numOfReadyNodes, numOfUnhealthyNodes, numOfReadyPods, numOfUnhealthyPods, clusterMemoryUsage, clusterCpuUsage10mAvg } from "../../lib/queries";
+
+export async function POST(req: NextRequest) {
+ try {
+
+ // Retrieving email from webhook URL query
+ const searchParams = req.nextUrl.searchParams
+ const email = searchParams.get("email")
+ console.log("email: ", email)
+ const alertObject = await req.json()
+ console.log(alertObject)
+
+ // Search and grab clusterID from the db based on the user email
+ const clusterIdQueryResult = await sql`SELECT cluster_id FROM users WHERE email=${email};`
+ const clusterId = clusterIdQueryResult[0]['cluster_id']
+ console.log(clusterId)
+
+ // Retrieving relevant incident details
+ const incident_title = alertObject['commonLabels']['alertname']
+ const priority_level = alertObject['commonLabels']['severity']
+ const incident_type = alertObject['commonAnnotations']['summary']
+ const incident_description = alertObject['commonAnnotations']['description']
+ const incident_status = 'Open'
+
+ // Insert new incident into the db
+ const newIncident = await sql`INSERT INTO incidents (cluster_id, incident_type, description, priority_level, incident_title, incident_status) VALUES (${clusterId}, ${incident_type}, ${incident_description}, ${priority_level}, ${incident_title}, ${incident_status}) RETURNING *;`
+ console.log('newIncident:', newIncident)
+ const newIncidentId = newIncident[0]['incident_id']
+
+ // Retrieving IP Address from db for PromQL queries for the metric snapshot
+ const clusterInformation = await sql`SELECT * FROM clusters WHERE cluster_id=${clusterId}`
+ const ipAddress = clusterInformation[0]['cluster_ip']
+
+ // Grabbing metrics snapshots with PromQL queries
+ const numOfReadyNodesResult = await numOfReadyNodes(ipAddress)
+ const numOfUnhealthyNodesResult = await numOfUnhealthyNodes(ipAddress)
+ const numOfReadyPodsResult = await numOfReadyPods(ipAddress)
+ const numOfUnhealthyPodsResult = await numOfUnhealthyPods(ipAddress)
+ const clusterMemoryUsageResult = await clusterMemoryUsage(ipAddress)
+ const clusterCpuUsage10mAvgResult = await clusterCpuUsage10mAvg(ipAddress)
+
+ //Adding the metrics to the db
+ await sql`INSERT INTO metric_data (incident_id, ready_nodes, unhealthy_nodes, ready_pods, unhealthy_pods, cluster_memory_usage, cluster_cpu_usage) VALUES (${newIncidentId}, ${numOfReadyNodesResult}, ${numOfUnhealthyNodesResult}, ${numOfReadyPodsResult}, ${numOfUnhealthyPodsResult}, ${clusterMemoryUsageResult}, ${clusterCpuUsage10mAvgResult})`
+
+ return NextResponse.json({status: 200})
+
+ }
+
+ catch(e) {
+ return NextResponse.json({message: 'Error occured while storing alert in db: ', e}, {status: 500})
+ }
+
+}
\ No newline at end of file
diff --git a/app/api/alertmanager/route.ts b/app/api/alertmanager/route.ts
new file mode 100644
index 0000000..61b1c44
--- /dev/null
+++ b/app/api/alertmanager/route.ts
@@ -0,0 +1,56 @@
+import { NextRequest, NextResponse } from "next/server";
+import sql from "../../utils/db";
+import { Incident } from "../../../types/definitions";
+import { numOfReadyNodes, numOfUnhealthyNodes, numOfReadyPods, numOfUnhealthyPods, clusterMemoryUsage, clusterCpuUsage10mAvg } from "../../lib/queries";
+
+export async function POST(req: NextRequest) {
+ try {
+
+ // Retrieving email from webhook URL query
+ const searchParams = req.nextUrl.searchParams
+ const email = searchParams.get("email")
+ console.log("email: ", email)
+ const alertObject = await req.json()
+ console.log(alertObject)
+
+ // Search and grab clusterID from the db based on the user email
+ const clusterIdQueryResult = await sql`SELECT cluster_id FROM users WHERE email=${email};`
+ const clusterId = clusterIdQueryResult[0]['cluster_id']
+ console.log(clusterId)
+
+ // Retrieving relevant incident details
+ const incident_title = alertObject['commonLabels']['alertname']
+ const priority_level = alertObject['commonLabels']['severity']
+ const incident_type = alertObject['commonAnnotations']['summary']
+ const incident_description = alertObject['commonAnnotations']['description']
+ const incident_status = 'Open'
+
+ // Insert new incident into the db
+ const newIncident = await sql`INSERT INTO incidents (cluster_id, incident_type, description, priority_level, incident_title, incident_status) VALUES (${clusterId}, ${incident_type}, ${incident_description}, ${priority_level}, ${incident_title}, ${incident_status}) RETURNING *;`
+ console.log('newIncident:', newIncident)
+ const newIncidentId = newIncident[0]['incident_id']
+
+ // Retrieving IP Address from db for PromQL queries for the metric snapshot
+ const clusterInformation = await sql`SELECT * FROM clusters WHERE cluster_id=${clusterId}`
+ const ipAddress = clusterInformation[0]['cluster_ip']
+
+ // Grabbing metrics snapshots with PromQL queries
+ const numOfReadyNodesResult = await numOfReadyNodes(ipAddress)
+ const numOfUnhealthyNodesResult = await numOfUnhealthyNodes(ipAddress)
+ const numOfReadyPodsResult = await numOfReadyPods(ipAddress)
+ const numOfUnhealthyPodsResult = await numOfUnhealthyPods(ipAddress)
+ const clusterMemoryUsageResult = await clusterMemoryUsage(ipAddress)
+ const clusterCpuUsage10mAvgResult = await clusterCpuUsage10mAvg(ipAddress)
+
+ //Adding the metrics to the db
+ await sql`INSERT INTO metric_data (incident_id, ready_nodes, unhealthy_nodes, ready_pods, unhealthy_pods, cluster_memory_usage, cluster_cpu_usage) VALUES (${newIncidentId}, ${numOfReadyNodesResult}, ${numOfUnhealthyNodesResult}, ${numOfReadyPodsResult}, ${numOfUnhealthyPodsResult}, ${clusterMemoryUsageResult}, ${clusterCpuUsage10mAvgResult})`
+
+ return NextResponse.json({status: 200})
+
+ }
+
+ catch(e) {
+ return NextResponse.json({message: 'Error occured while storing alert in db: ', e}, {status: 500})
+ }
+
+}
\ No newline at end of file
diff --git a/app/api/auth/[...nextauth]/authOptions.ts b/app/api/auth/[...nextauth]/authOptions.ts
new file mode 100644
index 0000000..4561c3f
--- /dev/null
+++ b/app/api/auth/[...nextauth]/authOptions.ts
@@ -0,0 +1,91 @@
+import { NextAuthOptions } from 'next-auth'
+import CredentialsProvider from 'next-auth/providers/credentials'
+import GithubProvider from 'next-auth/providers/github'
+import sql from '../../../utils/db'
+import bcrypt from 'bcrypt'
+
+export const authOptions: NextAuthOptions = {
+
+ providers: [
+ GithubProvider({
+ clientId: process.env.GITHUB_ID as string,
+ clientSecret: process.env.GITHUB_SECRET as string
+ }),
+
+ CredentialsProvider({
+ name: 'credentials',
+ credentials: {},
+
+ async authorize(credentials): Promise {
+ const {email, password} = credentials as {email: string, password: string}
+
+ try {
+ //Checking to see if the user exists
+ let res = await sql`SELECT * FROM users WHERE email=${email}`
+ if (!res.length) {
+ return null
+ }
+ //Checking to see if the password is correct
+ const passwordsMatch = await bcrypt.compare(password, res[0].password)
+ if (!passwordsMatch) {
+ return null
+ }
+ return res[0]
+ }
+ catch(e) {
+ console.log(e)
+ return null
+ }
+ },
+ })
+ ],
+ session: {
+ strategy: 'jwt',
+ maxAge: 2 * 60 * 60
+ },
+ secret: process.env.NEXTAUTH_SECRET,
+ pages: {
+ signIn:'/auth/login'
+ },
+ callbacks: {
+ async session({session, user}): Promise {
+ if (!session) return
+ try {
+ //Identifying the userid in the db to put in the session.user object
+ let res = await sql`SELECT * FROM users WHERE email=${session.user.email!}`
+
+ session.user.userid = res[0].user_id
+ return session
+ }
+ catch(e) {
+ console.log(e)
+ return null
+ }
+ },
+
+ async signIn({profile, account}): Promise {
+ //Checking to see if the user is using the Github oauth or local auth
+ if (account?.provider == 'credentials') {
+ return true
+ }
+ console.log(profile)
+
+ try {
+ //Checking to see if the user exists
+ //Typescript note: 'profile?.email!' has an explanation mark to ensure that this value will not be null
+ let res = await sql`SELECT * FROM users WHERE email=${profile?.email!}`
+ if (!res.length) {
+ //Inputing user into the db
+ await sql`INSERT INTO users (name, email) VALUES (${profile?.name as string}, ${profile?.email as string});`
+ }
+
+ return true
+ }
+ catch(e) {
+ console.log(e)
+ return
+ }
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts
new file mode 100644
index 0000000..86f2a5c
--- /dev/null
+++ b/app/api/auth/[...nextauth]/route.ts
@@ -0,0 +1,6 @@
+import { authOptions } from "./authOptions"
+import NextAuth from "next-auth"
+
+const handler = NextAuth(authOptions)
+
+export {handler as GET, handler as POST}
\ No newline at end of file
diff --git a/app/api/connect-cluster/route.ts b/app/api/connect-cluster/route.ts
new file mode 100644
index 0000000..5620908
--- /dev/null
+++ b/app/api/connect-cluster/route.ts
@@ -0,0 +1,60 @@
+import { NextResponse} from 'next/server';
+import sql from '../../utils/db';
+import { getServerSession } from 'next-auth';
+import { authOptions } from '../auth/[...nextauth]/authOptions'
+import { activeCluster } from '../../lib/queries';
+
+export async function POST(req: any) {
+
+ // This grabs the user id from the current logged in user
+ const session = await getServerSession(authOptions);
+ const user_id = session?.user.userid
+
+ // This is the cluster name and cluster ip that is sent from the request modal
+ const { clusterName, clusterIp } = await req.json()
+
+ try {
+ // This checks whether the logged in user has an associated cluster_id
+ if (user_id !== undefined) {
+ if (user_id !== undefined) {
+ const verifyUserCluster = await sql`SELECT cluster_id FROM users WHERE user_id = ${user_id}`
+
+ if (verifyUserCluster[0].cluster_id !== null) {
+ return NextResponse.json({ error: 'Error: You already have a cluster associated with your account!' }, { status: 400 })
+ }
+ }
+ }
+
+ // This checks whether the submitted cluster_ip from the popup modal already exists
+ const verifyClusterIp = await sql`SELECT cluster_ip from clusters WHERE cluster_ip = ${clusterIp}`
+
+ if (verifyClusterIp.length !== 0) {
+ console.log('clusterip already exists!')
+ return NextResponse.json({ error: 'Error: This clusterIp already exists!' }, { status: 400 })
+ }
+
+ // This checks whether the provided clusterip points to an active cluster
+ const activeClusterResults = await activeCluster(clusterIp)
+ if (!activeClusterResults) {
+ return NextResponse.json({ error: 'Error: This cluster is not active!' }, { status: 400 })
+ }
+
+ // This grabs the cluster_id and sends it back to the user so that cluster_id is associated with the user
+ const grabClusterId = await sql`
+ INSERT INTO clusters (cluster_name, cluster_ip) VALUES (${clusterName}, ${clusterIp}) RETURNING cluster_id`
+
+ const clusterId = grabClusterId[0].cluster_id
+
+ // This inserts into users cluster_ip
+ if (user_id !== undefined) {
+ const addClusterToUser = await sql`
+ UPDATE users SET cluster_id=${clusterId}, cluster_owner=TRUE WHERE user_id=${user_id}`
+ }
+
+ // If all the checks have passed, return true boolean
+ return NextResponse.json({newCluster: true});
+ } catch (err) {
+ console.error(`Error inserting data:`, err)
+ return NextResponse.json({newCluster: false})
+ }
+}
\ No newline at end of file
diff --git a/app/api/get-cluster/route.ts b/app/api/get-cluster/route.ts
new file mode 100644
index 0000000..035ad9e
--- /dev/null
+++ b/app/api/get-cluster/route.ts
@@ -0,0 +1,28 @@
+import { NextResponse } from "next/server";
+import sql from "../../utils/db";
+import { getServerSession } from "next-auth";
+import { authOptions } from "../auth/[...nextauth]/authOptions";
+
+export async function GET(req: any) {
+ // This grabs the user id from the current logged in user
+ const session = await getServerSession(authOptions);
+ const user_id = session?.user.userid;
+
+ try {
+ if (user_id !== undefined) {
+ // Select cluster_name and cluster_up based on the users_id
+ let clusterData = await sql`SELECT cluster_name, cluster_ip
+ FROM clusters
+ JOIN users ON users.cluster_id = clusters.cluster_id
+ WHERE users.user_id = ${user_id};`
+ if (clusterData.length === 0) {
+ return NextResponse.json({error: 'User Data Not Found'}, {status: 400})
+ }
+ const { cluster_ip, cluster_name }= clusterData[0]
+ // console.log(cluster_ip, cluster_name)
+ return NextResponse.json({cluster_ip, cluster_name})
+ }
+ } catch (err) {
+ console.error("Error. Not able to get user's clusters", err);
+ }
+}
diff --git a/app/api/getUser/[userID]/route.ts b/app/api/getUser/[userID]/route.ts
new file mode 100644
index 0000000..2864ec5
--- /dev/null
+++ b/app/api/getUser/[userID]/route.ts
@@ -0,0 +1,23 @@
+import { NextRequest, NextResponse} from 'next/server';
+import sql from '../../../utils/db';
+import { User } from '../../../../types/definitions';
+
+export async function GET(request: NextRequest, {params}: {params: {userID: string}}) {
+
+ const { userID } = params;
+
+ try {
+
+ const userData: User[] = await sql`
+ select * from users where user_id=${userID}
+ `
+
+ return NextResponse.json(userData[0]);
+
+ } catch(err) {
+ console.log('error', err)
+ return NextResponse.json({
+ message: `Error getting user data.`
+ });
+ }
+};
\ No newline at end of file
diff --git a/app/api/incidents/getAlerts/[user_id]/route.ts b/app/api/incidents/getAlerts/[user_id]/route.ts
new file mode 100644
index 0000000..efa7762
--- /dev/null
+++ b/app/api/incidents/getAlerts/[user_id]/route.ts
@@ -0,0 +1,34 @@
+import { NextRequest, NextResponse} from 'next/server';
+import sql from '../../../../utils/db';
+import { TableData, ClusterRes } from '../../../../../types/definitions';
+
+
+
+export async function GET(request: NextRequest, {params}: {params: {user_id: string}}) {
+
+ console.log('envorionment', process.env.NODE_ENV)
+
+ const { user_id } = params;
+
+ try {
+
+ const cluster_id: ClusterRes[] = await sql`
+ select cluster_id from users where user_id=${user_id}
+ `
+ const incidents: TableData[] = await sql`
+ select * from incidents left join clusters using (cluster_id) where cluster_id=${cluster_id[0].cluster_id}
+ `
+ const members: Array