diff --git a/README.md b/README.md index 537f1cdcc..1bc74acfe 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,37 @@ Chronos is a comprehensive developer tool that monitors the health and web traff ## What's New? -### Chronos 13.0 +### Chronos 14.0
+Contributors: +[Michael Tagg](https://github.com/mdtagg), +[Ted Pham](https://github.com/TedPham397), +[Sofia Sarhiri](https://github.com/sarhiri), +[Stephen Yang](https://github.com/stephenhyang), + +
+Updates: +
  • Implemented a new visualization button (example) to expedite data rendering processes.
  • +
  • Refactored microservices example, switched over to community version of MongoDB for easier URI implementation.
  • +
  • Modularized cluttered components into modular units to improve code readability and reusability.
  • +
  • Optimized startup scripts to reduce application load time and streamline environment configuration.
  • +
  • Upgraded Material-UI from version 4 to version 5, adapting to new API changes and improving UI responsiveness.
  • +
  • Refactored portions of the electron app
  • +
  • Refactored data parsing logic to reduce unnecessary rerenders and improve app performance
  • +
  • Fixed data bottle necks in the local npm packages, data is now able to flow to microservices for data visualization
  • + + +Version 14.0 Medium Article + +### **Iteration Log** + +
    Chronos 13.0 + +
    Chronos 12.0
    -### With Chronos 13.0 +### With Chronos 14.0 Chronos @@ -196,7 +226,8 @@ Updates: ## Overview of the CodeBase -- Instead of the typical folders & files view, a visual representation of the code is created. Below, it's showing the same repository, but instead of a directory structure, each file and folder as a circle: the circle’s color is the type of file, and the circle’s size represents the size of the file. See live demo here. +- If you want to visualize the way the files in the app are connected, we suggest using this data visualizer. Below, it's showing the same repository, but instead of a directory structure, each file and folder as a circle: the circle’s color is the type of file, and the circle’s size represents the size of the file. See live demo + here. codebase visulization ## Features @@ -259,11 +290,11 @@ export DISPLAY="`sed -n 's/nameserver //p' /etc/resolv.conf`:0" ### Running the Chronos desktop app in development mode (WSL Incompatible) 1. From the root directory, run `npm install` -2. Run `npm run build` -3. For Windows users, run `npm audit fix` or `npm audit fix --force` if prompted -4. Open a new terminal and run `npm run dev:app` to start the Webpack development server -5. Open a new terminal and run `npm run dev:electron` to start the Electron UI in development mode. -6. Refer to `Examples` sections below to spin up example applications. +2. Run `npm run start:electron` to start the electron app. +3. Run `npm audit fix` or `npm audit fix --force` if prompted +4. Refer to `Examples` sections below to spin up example applications. + (Recommended): + If you have mongo community edition running locally just run `npm run start:microservices` to start populating database with server data(more detail in Microservices Example section). # diff --git a/__backend-tests__/chronosMethods.test.js b/__backend-tests__/chronosMethods.test.js index fadd2d168..4b91e5dff 100644 --- a/__backend-tests__/chronosMethods.test.js +++ b/__backend-tests__/chronosMethods.test.js @@ -1,4 +1,4 @@ -const { EcoTwoTone } = require('@material-ui/icons'); +const { EcoTwoTone } = require('@mui/icons-material'); const Chronos = require('../chronos_npm_package/chronos.js'); const helpers = require('../chronos_npm_package/controllers/utilities.js'); const hpropagate = require('hpropagate'); diff --git a/__backend-tests__/mockdbsetup.js b/__backend-tests__/mockdbsetup.js index f4b4bf82a..07d3221c9 100644 --- a/__backend-tests__/mockdbsetup.js +++ b/__backend-tests__/mockdbsetup.js @@ -12,10 +12,10 @@ const connectDB = async () => { useNewUrlParser: true, useUnifiedTopology: true, }).then((result) => { - console.log(result.connection.readyState) - console.log(result.connection.host) + // console.log(result.connection.readyState) + // console.log(result.connection.host) }).catch((err) => { - console.log('Unable to connect to MongoMemoryServer') + // console.log('Unable to connect to MongoMemoryServer') }); }; diff --git a/__tests__/components/About.test.tsx b/__tests__/components/About.test.tsx index a579e0974..bbcc1a1a2 100644 --- a/__tests__/components/About.test.tsx +++ b/__tests__/components/About.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import About from '../../app/components/About'; +import About from '../../app/components/About/About'; import DashboardContextProvider from '../../app/context/DashboardContext'; describe('About Page', () => { @@ -22,7 +22,7 @@ describe('About Page', () => { expect(element.querySelectorAll('h3').length).toBe(3); }); - it('Should have one div', () => { - expect(element.querySelectorAll('div').length).toBe(1); + it('Should have three divs', () => { + expect(element.querySelectorAll('div').length).toBe(3); }); }); diff --git a/__tests__/components/Contact.test.tsx b/__tests__/components/Contact.test.tsx index d22330b66..d07d6c69f 100644 --- a/__tests__/components/Contact.test.tsx +++ b/__tests__/components/Contact.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import Contact from '../../app/components/Contact'; +import Contact from '../../app/components/Contact/Contact'; import DashboardContextProvider from '../../app/context/DashboardContext'; describe('Contact Page', () => { diff --git a/__tests__/components/Header.test.tsx b/__tests__/components/Header.test.tsx index ada95d59b..099fdbf30 100644 --- a/__tests__/components/Header.test.tsx +++ b/__tests__/components/Header.test.tsx @@ -2,7 +2,8 @@ /* eslint-disable import/no-named-as-default-member */ import React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; -import Header from '../../app/components/Header'; +import Header from '../../app/components/Header/Header'; +import ServiceDropdown from '../../app/components/Header/ServiceDropdown'; import { DashboardContext } from '../../app/context/DashboardContext'; import { ApplicationContext } from '../../app/context/ApplicationContext'; import { HashRouter as Router } from 'react-router-dom'; @@ -55,13 +56,59 @@ describe('Speed Chart', () => { }); // trying to test the functionality of component not passed as props - it('Should check/uncheck the checkbox when clicking services', () => { - // const checkBox = screen.getByRole('checkbox'); - // fireEvent.click(checkBox); - // expect(checkBox.parentElement).toHaveClass('selected'); - // fireEvent.click(checkBox); - // expect(checkBox.parentElement).not.toHaveClass('selected'); - }); + // it('Should check/uncheck the checkbox when clicking services', () => { + // const checkBox = screen.getByTestId('checkbox'); + // fireEvent.click(checkBox); + // expect(checkBox.parentElement).toHaveClass('selected'); + // fireEvent.click(checkBox); + // expect(checkBox.parentElement).not.toHaveClass('selected'); + // }); it('Should also change selectModal to true or false', () => {}); }); + +describe('ServiceDropdown test', () => { + it('opens and closes ServiceDropdown component on click', () => { + const servicesData = [ + { microservice: 'inventory' }, + { microservice: 'orders' }, + { microservice: 'auth' } + ]; + + // Define initial selected services state + const selectedServices = []; + + // Define a mock toggleDropdown function + const toggleDropdown = jest.fn(); + + // Render the ServiceDropdown component + render( + + ); + + // Assert that dropdown is initially closed + expect(screen.queryByText('inventory')).not.toBeInTheDocument(); + + // simulate click event on button within ServiceDropdown component + const selectButton = screen.getByTestId('ssButton'); + fireEvent.click(selectButton); + + // expect the toggleDropdown function to have been called + expect(toggleDropdown).toHaveBeenCalled(); + + // Assert that dropdown is now open by checking that service is rendered + // expect(screen.getByText('inventory')).toBeInTheDocument(); + + // // Simulate click event to close the dropdown + // fireEvent.click(selectButton); + + // // Assert that dropdown is now closed by checking that service is no longer rendered + // expect(screen.getByText('inventory')).not.toBeInTheDocument(); + }); +}); diff --git a/__tests__/components/Settings.test.tsx b/__tests__/components/Settings.test.tsx index 17c5c3470..ffab61f25 100644 --- a/__tests__/components/Settings.test.tsx +++ b/__tests__/components/Settings.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; -import Settings from '../../app/components/Settings'; +import Settings from '../../app/components/Setting/Setting'; import { DashboardContext } from '../../app/context/DashboardContext'; import '@testing-library/jest-dom'; diff --git a/__tests__/components/SignUp.test.tsx b/__tests__/components/SignUp.test.tsx index 1f75f90a8..82c9b2ada 100644 --- a/__tests__/components/SignUp.test.tsx +++ b/__tests__/components/SignUp.test.tsx @@ -7,7 +7,7 @@ import { HashRouter as Router } from 'react-router-dom'; jest.mock('electron', () => ({ ipcRenderer: { sendSync: jest.fn() } })); -describe('Create Signup Page', () => { +xdescribe('Create Signup Page', () => { beforeEach(() => { render( @@ -18,11 +18,11 @@ describe('Create Signup Page', () => { ); }); - it('should render', () => { + xit('should render', () => { expect(screen).toBeTruthy(); }); - it('Should contain an h1, h2, form, two buttons, and three inputs', () => { + xit('Should contain an h1, h2, form, two buttons, and three inputs', () => { const element = screen.getByTestId('SignUp'); expect(element.querySelectorAll('h1').length).toBe(1); expect(element.querySelectorAll('h2').length).toBe(1); @@ -31,7 +31,7 @@ describe('Create Signup Page', () => { expect(element.querySelectorAll('input').length).toBe(4); }); - it('Sign up button should submit email, username, and password to addUser', async () => { + xit('Sign up button should submit email, username, and password to addUser', async () => { screen.debug(); const username = screen.getByPlaceholderText('enter username'); diff --git a/app/App.tsx b/app/App.tsx index b51239b56..36be7d732 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; -import Splash from './components/Splash'; -import DashboardContainer from './containers/DashboardContainer'; -import './stylesheets/scrollBar.scss'; +import Splash from './components/Splash/Splash'; +import DashboardContainer from './containers/DashboardContainer/DashboardContainer'; +import './index.scss'; // this is the fitness gram pacer test diff --git a/app/assets/search.svg b/app/assets/search.svg deleted file mode 100644 index cd27601f4..000000000 --- a/app/assets/search.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/charts/AwsChart.tsx b/app/charts/AwsChart.tsx index a0cc65312..cabd14c1e 100644 --- a/app/charts/AwsChart.tsx +++ b/app/charts/AwsChart.tsx @@ -3,21 +3,20 @@ import React, { useState } from 'react'; import Plot from 'react-plotly.js'; import { all, solo as soloStyle } from './sizeSwitch'; -// interface AwsCpuChartProps { -// key: string; -// renderService: string; -// metric: string; -// timeList: any; -// valueList: any; -// sizing: string; -// colourGenerator: Function; -// } - interface SoloStyles { height: number; width: number; } +interface IPlotlyData { + name: any; + x: any; + y: any; + type: any; + mode: any; + marker: { color: string }; +} + /** * @params props - the props object containing relevant data. * @desc Handles AWS Charts. Memoized component to generate an AWS chart with formatted data. @@ -26,34 +25,31 @@ interface SoloStyles { const AwsChart: React.FC = React.memo(props => { const { renderService, metric, timeList, valueList, colourGenerator, sizing } = props; const [solo, setSolo] = useState(null); + setInterval(() => { if (solo !== soloStyle) { setSolo(soloStyle); } }, 20); - const createChart = () => { - const timeArr = timeList?.map((el: any) => moment(el).format('kk:mm:ss')); - // const hashedColour = colourGenerator(renderService); - let plotlyData: { - name: any; - x: any; - y: any; - type: any; - mode: any; - marker: { color: string }; - }; - plotlyData = { - name: metric, - x: timeArr, - y: valueList, - type: 'scattergl', - mode: 'lines', - marker: { color: colourGenerator() }, - }; - const sizeSwitch = sizing === 'all' ? all : solo; + const timeArr = timeList?.map((el: any) => moment(el).format('kk:mm:ss')); + + let plotlyData:IPlotlyData= { + name: metric, + x: timeArr, + y: valueList, + type: 'scattergl', + mode: 'lines', + marker: { color: colourGenerator() }, + }; - return ( + const sizeSwitch = sizing === 'all' ? all : solo; + + return ( +
    = React.memo(props => { }, }} /> - ); - }; - - return ( -
    - {createChart()}
    ); }); diff --git a/app/charts/GrafanaEventChart.tsx b/app/charts/GrafanaEventChart.tsx deleted file mode 100644 index 8ed5ca339..000000000 --- a/app/charts/GrafanaEventChart.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React, { useState } from 'react'; -import { all, solo as soloStyle } from './sizeSwitch'; -import '../stylesheets/GrafanaGraph.scss'; - -interface EventChartProps { - metricName: string; - token: string; -} - - -type TimeFrame = '5m' | '15m' | '30m' | '1h' | '2h' | '1d' | '2d'; - - - -/** - * @params {EventChartProps} props - the props object containing relevant data. - * @desc Handles k8s and container metrics. Memoized component to generate event chart with formatted data - * @returns {JSX.Element} The JSX element with the event chart. - */ -const GrafanaEventChart: React.FC = React.memo(props => { - const { metricName, token } = props; - const [graphType, setGraphType] = useState("timeseries"); - const [type, setType] = useState(['timeserie']); - const [timeFrame, setTimeFrame] = useState('5m'); - - console.log("graphType: ", graphType) - console.log("type: ", type) - console.log("inside GrafanaEventChart") - - console.log("metricName: ", metricName) - let uid = metricName.replace(/.*\/.*\//g, '') - if (uid.length >= 40) { - uid = metricName.slice(metricName.length - 39); - } - - let parsedName = metricName.replace(/.*\/.*\//g, '') - console.log("uid: ", uid) - console.log("parsedName: ", parsedName) - - const handleSelectionChange = async (event) => { - setType([...type, graphType]); - await fetch('http://localhost:1111/api/updateDashboard', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ graphType: event.target.value, metric: metricName, token: token }), - }) - console.log("event.target.value: ", event.target.value) - setGraphType(event.target.value); - } - - - return ( -
    -

    {`${parsedName} --- ${graphType}`}

    -
    - - - - - -
    - {/* create chart using grafana iframe tag*/} - {/* {type[type.length - 1] !== graphType ? - - : } */} - {graphType === "timeseries" ? TimeSeries(uid, parsedName, graphType, timeFrame) : - graphType === "barchart" ? BarChart(uid, parsedName, graphType, timeFrame) : - graphType === "stat" ? Stat(uid, parsedName, graphType, timeFrame) : - graphType === "gauge" ? Gauge(uid, parsedName, graphType, timeFrame) : - graphType === "table" ? Table(uid, parsedName, graphType, timeFrame) : - graphType === "histogram" ? Histogram(uid, parsedName, graphType, timeFrame) : - graphType === "piechart" ? PieChart(uid, parsedName, graphType, timeFrame) : - graphType === "alertlist" ? AlertList(uid, parsedName, graphType, timeFrame) : - null} - -
    - ); -}); - -const TimeSeries = (uid, parsedName, graphType, timeFrame) => { - return <> - -
    - -} - -const BarChart = (uid, parsedName, graphType, timeFrame) => { - return -} - -const Stat = (uid, parsedName, graphType, timeFrame) => { - return -} - -const Gauge = (uid, parsedName, graphType, timeFrame) => { - return -} - -const Table = (uid, parsedName, graphType, timeFrame) => { - return -} - -const Histogram = (uid, parsedName, graphType, timeFrame) => { - return -} - -const PieChart = (uid, parsedName, graphType, timeFrame) => { - return -} - -const AlertList = (uid, parsedName, graphType, timeFrame) => { - return -} -export default GrafanaEventChart; diff --git a/app/charts/GrafanaEventChart/GrafanaEventChart.tsx b/app/charts/GrafanaEventChart/GrafanaEventChart.tsx new file mode 100644 index 000000000..4728e7d96 --- /dev/null +++ b/app/charts/GrafanaEventChart/GrafanaEventChart.tsx @@ -0,0 +1,196 @@ +import React, { useState } from 'react'; +import { all, solo as soloStyle } from '../sizeSwitch'; +import './styles.scss'; + +interface EventChartProps { + metricName: string; + token: string; +} + +type TimeFrame = '5m' | '15m' | '30m' | '1h' | '2h' | '1d' | '2d'; + +/** + * @params {EventChartProps} props - the props object containing relevant data. + * @desc Handles k8s and container metrics. Memoized component to generate event chart with formatted data + * @returns {JSX.Element} The JSX element with the event chart. + */ +const GrafanaEventChart: React.FC = React.memo(props => { + const { metricName, token } = props; + const [graphType, setGraphType] = useState('timeseries'); + const [type, setType] = useState(['timeserie']); + const [timeFrame, setTimeFrame] = useState('5m'); + + // console.log("graphType: ", graphType) + // console.log("type: ", type) + // console.log("inside GrafanaEventChart") + + // console.log("metricName: ", metricName) + let uid = metricName.replace(/.*\/.*\//g, ''); + if (uid.length >= 40) { + uid = metricName.slice(metricName.length - 39); + } + + let parsedName = metricName.replace(/.*\/.*\//g, ''); + // console.log("uid: ", uid) + // console.log("parsedName: ", parsedName) + + const handleSelectionChange = async event => { + setType([...type, graphType]); + await fetch('http://localhost:1111/api/updateDashboard', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ graphType: event.target.value, metric: metricName, token: token }), + }); + console.log('event.target.value: ', event.target.value); + setGraphType(event.target.value); + }; + + return ( +
    +

    {`${parsedName} --- ${graphType}`}

    +
    + + + + + +
    + {/* create chart using grafana iframe tag*/} + {/* {type[type.length - 1] !== graphType ? + + : } */} + {graphType === 'timeseries' + ? TimeSeries(uid, parsedName, graphType, timeFrame) + : graphType === 'barchart' + ? BarChart(uid, parsedName, graphType, timeFrame) + : graphType === 'stat' + ? Stat(uid, parsedName, graphType, timeFrame) + : graphType === 'gauge' + ? Gauge(uid, parsedName, graphType, timeFrame) + : graphType === 'table' + ? Table(uid, parsedName, graphType, timeFrame) + : graphType === 'histogram' + ? Histogram(uid, parsedName, graphType, timeFrame) + : graphType === 'piechart' + ? PieChart(uid, parsedName, graphType, timeFrame) + : graphType === 'alertlist' + ? AlertList(uid, parsedName, graphType, timeFrame) + : null} +
    + ); +}); + +const TimeSeries = (uid, parsedName, graphType, timeFrame) => { + return ( + <> + +
    + + ); +}; + +const BarChart = (uid, parsedName, graphType, timeFrame) => { + return ( + + ); +}; + +const Stat = (uid, parsedName, graphType, timeFrame) => { + return ( + + ); +}; + +const Gauge = (uid, parsedName, graphType, timeFrame) => { + return ( + + ); +}; + +const Table = (uid, parsedName, graphType, timeFrame) => { + return ( + + ); +}; + +const Histogram = (uid, parsedName, graphType, timeFrame) => { + return ( + + ); +}; + +const PieChart = (uid, parsedName, graphType, timeFrame) => { + return ( + + ); +}; + +const AlertList = (uid, parsedName, graphType, timeFrame) => { + return ( + + ); +}; +export default GrafanaEventChart; diff --git a/app/stylesheets/GrafanaGraph.scss b/app/charts/GrafanaEventChart/styles.scss similarity index 100% rename from app/stylesheets/GrafanaGraph.scss rename to app/charts/GrafanaEventChart/styles.scss diff --git a/app/charts/LogsTable.jsx b/app/charts/LogsTable.jsx index b5fbc5292..83bd73a16 100644 --- a/app/charts/LogsTable.jsx +++ b/app/charts/LogsTable.jsx @@ -9,16 +9,20 @@ import { useTable, useGroupBy, useExpanded } from 'react-table'; import { CommsContext } from '../context/CommsContext'; + /** * Styling for the Logs Table */ const Styles = styled.div` padding: 1rem; + table { border-spacing: 0; border: 1px solid black; + background-color: #D7D7DC; + tr { :last-child { @@ -158,10 +162,10 @@ const LogsTable = () => { ); const data = useContext(CommsContext).commsData; - + return ( - - + +
    ); }; diff --git a/app/charts/RequestTypesChart.tsx b/app/charts/RequestTypesChart.tsx index 816cc47e1..8e77e75ba 100644 --- a/app/charts/RequestTypesChart.tsx +++ b/app/charts/RequestTypesChart.tsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react'; import Plot from 'react-plotly.js'; import { CommsContext } from '../context/CommsContext'; -import '../stylesheets/constants.scss'; +import '../index.scss'; const RequestTypesChart: React.FC = React.memo(() => { const { commsData } = useContext(CommsContext); diff --git a/app/charts/RouteChart.jsx b/app/charts/RouteChart.jsx index b3e139a90..ca6e2c317 100644 --- a/app/charts/RouteChart.jsx +++ b/app/charts/RouteChart.jsx @@ -1,4 +1,4 @@ -import { makeStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@mui/styles'; import React, { useContext } from 'react'; import Graph from 'react-graph-vis'; import { CommsContext } from '../context/CommsContext'; diff --git a/app/components/About.tsx b/app/components/About.tsx deleted file mode 100644 index a36838802..000000000 --- a/app/components/About.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { useContext } from 'react'; -import '../stylesheets/About.scss'; -import * as DashboardContext from '../context/DashboardContext'; -import lightAndDark from './Styling'; - -const About: React.FC = React.memo(() => { - const { mode } = useContext(DashboardContext.DashboardContext); - - const currentMode = - mode === 'light' ? lightAndDark.lightModeText : lightAndDark.darkModeText; - - /** - * Enter your OSP group's names into the explicit array of names in nameArray, and it will render them Chronos appropriately. - * Feel free to change the header for the list of names. - * - */ - const nameArray: JSX.Element[] = ['Haoyu', 'Eisha', 'Edwin', 'Tyler'].map(name => { - return ( - -

    {`${name}`}

    -
    - ); - }); - - return ( -
    -
    -

    - About -

    -

    - The Chronos Team has a passion for building tools that are powerful, beautifully - designed, and easy to use. Chronos was conceived as an Open Source endeavor that directly benefits the developer - community. Together, the Chronos application and NPM package make up an all-in-one network and health monitoring - tool for your containerized or non-conatinerized applications or microservices. It can also - monitor applications deployed using AWS, EC2, and ECS from Amazon. -

    -

    -

    - Current Version Authors -

    -
    - {nameArray} -
    -
    -

    - Past Contributors -

    -

    - Snow, Taylor, Tim, Roberto, Nachiket, Tiffany, Bruno, Danny, Vince, Matt, Derek, Kit, - Grace, Jen, Patty, Stella, Michael, Ronelle, Todd, Greg, Brianna, Brian, Alon, Alan, - Ousman, Ben, Chris, Jenae, Tim, Kirk, Jess, William, Alexander, Elisa, Josh, Troy, Gahl, - Brisa, Kelsi, Lucie, Jeffrey, Justin -

    -
    -
    -
    - ); -}); - -export default About; diff --git a/app/components/About/About.tsx b/app/components/About/About.tsx new file mode 100644 index 000000000..c547b9ee5 --- /dev/null +++ b/app/components/About/About.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import './styles.scss'; +import TeamMembers from './TeamMembers'; +import PastContributors from './PastContributors'; +// import { useStylingContext } from './StylingContext'; +import { useContext } from 'react'; +import lightAndDark from '../Styling'; +import { DashboardContext } from '../../context/DashboardContext'; + +const About: React.FC = React.memo(() => { + const { mode } = useContext(DashboardContext); + + const currentMode = mode === 'light' ? lightAndDark.lightModeText : lightAndDark.darkModeText; + const currentStyle = mode === 'light' ? lightAndDark.lightModeData : lightAndDark.darkModeData; + return ( + + +
    +
    +

    About

    +

    + The Chronos Team has a passion for building tools that are powerful, beautifully + designed, and easy to use. Chronos was conceived as an Open Source endeavor that directly benefits the developer + community. It can also monitor applications deployed using AWS, EC2, and ECS from Amazon. +

    +

    + + +
    +
    + ); +}); + +export default About; \ No newline at end of file diff --git a/app/components/About/PastContributors.tsx b/app/components/About/PastContributors.tsx new file mode 100644 index 000000000..6ef4664d0 --- /dev/null +++ b/app/components/About/PastContributors.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { useStylingContext } from './StylingContext'; + +const contributors = [ + 'Haoyu', 'Eisha', 'Edwin', 'Tyler', 'Snow', 'Taylor', 'Tim', 'Roberto', 'Nachiket', 'Tiffany', 'Bruno', 'Danny', 'Vince', + 'Matt', 'Derek', 'Kit', 'Grace', 'Jen', 'Patty', 'Stella', 'Michael', 'Ronelle', 'Todd', + 'Greg', 'Brianna', 'Brian', 'Alon', 'Alan', 'Ousman', 'Ben', 'Chris', 'Jenae', 'Tim', + 'Kirk', 'Jess', 'William', 'Alexander', 'Elisa', 'Josh', 'Troy', 'Gahl', 'Brisa', 'Kelsi', + 'Lucie', 'Jeffrey', 'Justin' +]; + +const PastContributors: React.FC = () => { + const currentMode = useStylingContext(); + + return ( +
    +

    + Past Contributors +

    +

    + {contributors.join(', ')} +

    +
    +
    + ); +}; + +export default PastContributors; \ No newline at end of file diff --git a/app/components/About/StylingContext.tsx b/app/components/About/StylingContext.tsx new file mode 100644 index 000000000..b99f255f0 --- /dev/null +++ b/app/components/About/StylingContext.tsx @@ -0,0 +1,9 @@ +import { useContext } from 'react'; +import { DashboardContext } from '../../context/DashboardContext'; +import lightAndDark from '../Styling'; + +export const useStylingContext = () => { + const { mode } = useContext(DashboardContext); + const currentMode = mode === 'light' ? lightAndDark.lightModeText : lightAndDark.darkModeText; + return currentMode; +}; \ No newline at end of file diff --git a/app/components/About/TeamMembers.tsx b/app/components/About/TeamMembers.tsx new file mode 100644 index 000000000..396821918 --- /dev/null +++ b/app/components/About/TeamMembers.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { useStylingContext } from './StylingContext'; + +const names = ['Mike', 'Stephen', 'Ted', 'Sofia']; + +const TeamMembers: React.FC = () => { + const currentMode = useStylingContext(); + + const nameList = names.map(name => ( + +

    {name}

    +
    + )); + + return ( +
    +

    + Current Version Authors +

    +
    + {nameList} +
    +
    +
    + ); +}; + +export default TeamMembers; \ No newline at end of file diff --git a/app/stylesheets/About.scss b/app/components/About/styles.scss similarity index 91% rename from app/stylesheets/About.scss rename to app/components/About/styles.scss index b2040fba1..23a6682dc 100644 --- a/app/stylesheets/About.scss +++ b/app/components/About/styles.scss @@ -1,13 +1,13 @@ -@import './constants.scss'; +@import '../../index.scss'; .about { min-width: 421px; max-width: 600px; - margin: auto 20px; + margin: auto; display: flex; justify-content: center; align-items: center; - width: 90%; + width: 100%; height: 100%; } @@ -30,10 +30,10 @@ } .text { - font-size: 18px; + font-size: 20px; color: $background; text-align: left; - font-weight: 100; + font-weight: 300; } .blurb { diff --git a/app/components/ApplicationsCard/ApplicationsCard.tsx b/app/components/ApplicationsCard/ApplicationsCard.tsx new file mode 100644 index 000000000..6847fd457 --- /dev/null +++ b/app/components/ApplicationsCard/ApplicationsCard.tsx @@ -0,0 +1,68 @@ + +import React from "react"; +import { Card,CardHeader,IconButton,CardContent,Typography } from "@mui/material"; +import HighlightOffIcon from '@mui/icons-material/HighlightOff'; +import UpdateIcon from '@mui/icons-material/Update'; +import './styles.scss'; +import { getEventHandlers } from './EventHandlers'; + +interface ApplicationCardProps { + application: any[]; + i: number; + setModal: (modalState: { isOpen: boolean; type: string }) => void; + classes: any; +} + +import './styles.scss' + +const ApplicationsCard:React.FC = (props) => { + + const { application, i, setModal, classes } = props + const { handleClick, confirmDelete } = getEventHandlers({ application, setModal }); + + return ( +
    + handleClick(application[0], application[3], i)} + > +
    +
    +
    + + confirmDelete(event, i)} + size="large" + > + + + } + /> + + + {application[0]} + +

    Service:

    + {application[3]} +
    +
    + +
    + + +

    {application[4]}

    +
    +
    +
    +
    + ); +}; + +export default ApplicationsCard; diff --git a/app/components/ApplicationsCard/EventHandlers.ts b/app/components/ApplicationsCard/EventHandlers.ts new file mode 100644 index 000000000..6642ed3c6 --- /dev/null +++ b/app/components/ApplicationsCard/EventHandlers.ts @@ -0,0 +1,46 @@ +import { useContext } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { DashboardContext } from '../../context/DashboardContext'; +import { ApplicationContext } from '../../context/ApplicationContext'; + +type ClickEvent = React.MouseEvent; + +interface EventHandlersProps { + application: any[]; + setModal: (modalState: { isOpen: boolean; type: string }) => void; +} + +export const getEventHandlers = ({ application, setModal }: EventHandlersProps) => { + const { deleteApp, user } = useContext(DashboardContext); + const { setAppIndex, setApp, setServicesData, app, example, connectToDB, setChart } = useContext(ApplicationContext); + const navigate = useNavigate(); + + const handleClick = (selectedApp: string, selectedService: string, i: number) => { + // const services = ['auth', 'client', 'event-bus', 'items', 'inventory', 'orders']; + const services = [ 'client', 'items','event-bus']; + + setAppIndex(i); + setApp(selectedApp); + if (['AWS', 'AWS/EC2', 'AWS/ECS', 'AWS/EKS'].includes(selectedService)) { + navigate(`/aws/:${app}`, { state: { typeOfService: selectedService } }); + } + else if (example) { + setServicesData([]); + setChart('all'); + connectToDB(user, i, app, application[2], application[1]); + navigate(`/applications/example/${services.join(' ')}`); + } + else { + setServicesData([]); + setModal({ isOpen: true, type: 'serviceModal' }); + } + }; + + const confirmDelete = (event: ClickEvent, i: number) => { + event.stopPropagation(); + const message = `The application '${app}' will be permanently deleted. Continue?`; + if (confirm(message)) deleteApp(i, ''); + }; + + return { handleClick, confirmDelete }; +}; diff --git a/app/components/ApplicationsCard/styles.scss b/app/components/ApplicationsCard/styles.scss new file mode 100644 index 000000000..8fef07fab --- /dev/null +++ b/app/components/ApplicationsCard/styles.scss @@ -0,0 +1,246 @@ +@import '../../index.scss'; + +.card { + display: flex; + flex-direction: row; + justify-content: space-around; + margin: 20px; + padding: 0; + cursor: pointer; + transition: all 0.5s; + + &:after, + &:before { + content: ' '; + width: 10px; + height: 10px; + position: absolute; + transition: all 0.5s; + } + + &:hover { + position: relative; + border-top-right-radius: 0px; + border-bottom-left-radius: 0px; + + &:before, + &:after { + width: 25%; + height: 25%; + } + } +} + +.card { + &:hover .databaseIconHeader { + visibility: hidden; + // background-color: $gblue; + // opacity: 0.7; + // box-shadow: 0 4px 20px 0 rgba(0, 0, 0,.14), 0 7px 10px -5px rgba(255, 255, 255, 0.4); + } + + &:hover p { + color: $background; + font-weight: 400; + } + + &:hover .cardFooter { + color: $background; + } + + &:hover .cardLine { + background-color: $background; + } + + &:hover #cardFooterText { + color: $background; + } + + &:hover .cardFooterIcon { + color: $background; + } + + &:hover #databaseName { + color: $background; + } +} + +#card-MongoDB { + &:hover .databaseIconHeader { + visibility: hidden; + // background-color: $gblue; + // opacity: 0.7; + // box-shadow: 0 4px 20px 0 rgba(0, 0, 0,.14), 0 7px 10px -5px rgba(255, 255, 255, 0.4); + } + + .databaseIconContainer { + display: inline-block; + overflow: visible; + } + + .databaseIconHeader { + position: absolute; + background-color: $ggreen; + display: flex; + justify-content: center; + align-items: center; + width: 90px; + height: 90px; + padding: 15px; + float: left; + top: -20px; + left: 180px; + border-radius: 3px; + box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.14), 0 7px 10px -5px rgba(0, 255, 42, 0.4); + background-image: url('../../assets/mongo-icon-white.png'); + background-position: center; + background-size: 70%; + background-repeat: no-repeat; + + .databaseIcon { + width: 55px; + height: 55px; + visibility: hidden; + } + } +} + +#card-SQL { + &:hover .databaseIconHeader { + visibility: hidden; + // background-color: $gblue; + // opacity: 0.7; + // box-shadow: 0 4px 20px 0 rgba(0, 0, 0,.14), 0 7px 10px -5px rgba(255, 255, 255, 0.4); + } + + .databaseIconContainer { + display: inline-block; + overflow: visible; + } + + .databaseIconHeader { + position: absolute; + background-color: $gyellow; + display: flex; + justify-content: center; + align-items: center; + width: 90px; + height: 90px; + padding: 15px; + float: left; + top: -20px; + left: 180px; + border-radius: 3px; + box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.14), 0 7px 10px -5px rgba(255, 152, 0, 0.4); + background-image: url('../../assets/postgres-icon-white.png'); + background-size: cover; + + .databaseIcon { + visibility: hidden; + } + } +} + +#card-AWS { + &:hover .databaseIconHeader { + visibility: hidden; + // background-color: $gblue; + // opacity: 0.7; + // box-shadow: 0 4px 20px 0 rgba(0, 0, 0,.14), 0 7px 10px -5px rgba(255, 255, 255, 0.4); + } + + .databaseIconContainer { + display: inline-block; + overflow: visible; + } + + .databaseIconHeader { + position: absolute; + background-color: $gorange; + display: flex; + justify-content: center; + align-items: center; + width: 90px; + height: 90px; + padding: 15px; + float: left; + top: -20px; + left: 180px; + border-radius: 3px; + box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.14), 0 7px 10px -5px rgba(255, 152, 0, 0.4); + background-image: url('../../assets/aws-icon-white.png'); + background-size: cover; + + .databaseIcon { + visibility: hidden; + } + } +} + +.databaseIconContainer { + display: inline-block; + overflow: visible; +} + +.databaseIconHeader { + position: absolute; + background-color: $ggreen; + display: flex; + justify-content: center; + align-items: center; + width: 90px; + height: 90px; + padding: 15px; + float: left; + top: -20px; + left: 180px; + border-radius: 3px; + box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.14), 0 7px 10px -5px rgba(0, 255, 42, 0.4); + + .databaseIcon { + width: 55px; + height: 55px; + } +} + +.cardLine { + background-color: $grey; + width: 85%; + border: none; + height: 1px; + margin-top: 20px; + margin-bottom: 15px; +} + +.cardFooter { + width: 90%; + display: flex; + height: 20px; + align-items: center; + margin-left: 18px; +} + +#cardFooterText { + color: $icon; + font-size: 11px; + margin: 0; + margin-left: 10px; +} + +.cardFooterIcon { + color: $icon; + font-size: 14px; + margin: 0; +} + +#databaseName { + margin-top: 14px; + margin-bottom: 0; + font-size: 40px; + width: 280px; +} + +#serviceName { + font-size: 11px; + margin-top: 6px; +} diff --git a/app/components/AwsEC2Graphs.tsx b/app/components/AwsEC2Graphs/AwsEC2Graphs.tsx similarity index 57% rename from app/components/AwsEC2Graphs.tsx rename to app/components/AwsEC2Graphs/AwsEC2Graphs.tsx index 2bedae829..0c585fd5a 100644 --- a/app/components/AwsEC2Graphs.tsx +++ b/app/components/AwsEC2Graphs/AwsEC2Graphs.tsx @@ -1,8 +1,8 @@ import React, { useContext, useEffect, useState } from 'react'; -import AwsChart from '../charts/AwsChart'; -import { AwsContext } from '../context/AwsContext'; -import { CircularProgress } from '@material-ui/core'; -import zIndex from '@material-ui/core/styles/zIndex'; +import AwsChart from '../../charts/AwsChart'; +import { AwsContext } from '../../context/AwsContext'; +import { stringToColor } from '../../utils'; +import './styles.scss' const AwsEC2Graphs: React.FC = React.memo(props => { const { awsData, setAwsData, isLoading, setLoadingState } = useContext(AwsContext); @@ -14,24 +14,6 @@ const AwsEC2Graphs: React.FC = React.memo(props => { }; }, []); - const stringToColor = (string: string, recurses = 0) => { - if (recurses > 20) return string; - function hashString(str: string) { - let hash = 0; - for (let i = 0; i < str.length; i++) { - hash = str.charCodeAt(i) + ((hash << 5) - hash); - } - let colour = '#'; - for (let i = 0; i < 3; i++) { - const value = (hash >> (i * 8)) & 0xff; - colour += `00${value.toString(16)}`.substring(-2); - } - - console.log(colour); - return colour; - } - }; - return (
    {Object.keys(awsData)?.map(metric => { diff --git a/app/components/AwsEC2Graphs/styles.scss b/app/components/AwsEC2Graphs/styles.scss new file mode 100644 index 000000000..0fee81664 --- /dev/null +++ b/app/components/AwsEC2Graphs/styles.scss @@ -0,0 +1,10 @@ +.charts { + display: grid; + grid-template-columns: auto auto; + padding: 10px; +} + +.chart { + margin: 10px; + text-align: center; +} \ No newline at end of file diff --git a/app/components/AwsECSClusterGraphs.tsx b/app/components/AwsECSClusterGraphs.tsx index 81df03014..5c29bc268 100644 --- a/app/components/AwsECSClusterGraphs.tsx +++ b/app/components/AwsECSClusterGraphs.tsx @@ -1,5 +1,6 @@ import React, { useContext, useEffect } from 'react'; import AwsChart from '../charts/AwsChart'; +import { stringToColor } from '../utils'; import { AwsContext } from '../context/AwsContext'; const AwsECSClusterGraphs: React.FC = React.memo(props => { @@ -12,24 +13,6 @@ const AwsECSClusterGraphs: React.FC = React.memo(props => { }; }, []); - const stringToColor = (string: string, recurses = 0) => { - if (recurses > 20) return string; - function hashString(str: string) { - let hash = 0; - for (let i = 0; i < str.length; i++) { - hash = str.charCodeAt(i) + ((hash << 5) - hash); - } - let colour = '#'; - for (let i = 0; i < 3; i++) { - const value = (hash >> (i * 8)) & 0xff; - colour += `00${value.toString(16)}`.substring(-2); - } - - console.log(colour); - return colour; - } - }; - const activeServices = Object.keys(awsEcsData) .slice(1) .filter(el => awsEcsData[el].CPUUtilization?.value.length > 0); diff --git a/app/components/ClusterTable.tsx b/app/components/ClusterTable.tsx index 676eb989c..d1dd88c72 100644 --- a/app/components/ClusterTable.tsx +++ b/app/components/ClusterTable.tsx @@ -7,8 +7,8 @@ import { TableHead, TableRow, Paper, -} from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; +} from '@mui/material'; +import { makeStyles } from '@mui/styles/'; import { AwsContext } from '../context/AwsContext'; const useStyles = makeStyles({ diff --git a/app/components/Contact.tsx b/app/components/Contact.tsx deleted file mode 100644 index f50a061ee..000000000 --- a/app/components/Contact.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* eslint-disable jsx-a11y/label-has-associated-control */ -import React, { useContext } from 'react'; -import '../stylesheets/Contact.scss'; -import { DashboardContext } from '../context/DashboardContext'; -import lightAndDark from './Styling'; - -const Contact:React.FC = React.memo(() => { - const { mode } = useContext(DashboardContext); - - const currentMode = - mode === 'light' ? lightAndDark.lightModeText : lightAndDark.darkModeText; - - return ( -
    -
    -
    -
    -

    Contact Us

    -
    -

    - Please feel free to provide any feedback, concerns, or comments. -

    -

    - You can find issues the team is currently working on{' '} - - here - - . -

    -
    -
    -
    -
    - - - - -