├── .gitignore ├── __mocks__ ├── styleMock.js └── fileMock.js ├── .DS_Store ├── dump.rdb ├── client ├── .DS_Store ├── components │ ├── .DS_Store │ ├── StyledComponents │ │ ├── .DS_Store │ │ ├── variables.js │ │ ├── Themes.js │ │ ├── HomePage.js │ │ ├── MessageModal.js │ │ ├── GraphGrid.js │ │ ├── GlobalStyle.js │ │ ├── Form.js │ │ └── SideBar.js │ ├── Toggler.jsx │ ├── LoadingGraphPage.jsx │ ├── MessageModal.jsx │ ├── BasicActivities │ │ ├── Keyspace.jsx │ │ ├── ConnectedSlaves.jsx │ │ ├── ConnectedClient.jsx │ │ └── ActivitiesPage.jsx │ ├── Persistence │ │ ├── Rlst.jsx │ │ ├── Rcslt.jsx │ │ └── PersistencePage.jsx │ ├── Performance │ │ ├── Latency.jsx │ │ ├── HitRate.jsx │ │ ├── PerformancePage.jsx │ │ └── Iops.jsx │ ├── Errors │ │ ├── keyspaceMisses.jsx │ │ ├── RejectedConnections.jsx │ │ └── ErrorsPage.jsx │ ├── HomePage.jsx │ ├── Memory │ │ ├── MemoryPage.jsx │ │ ├── UsedMemoryGraph.jsx │ │ ├── EvictedKeys.jsx │ │ └── FragRatioGraph.jsx │ ├── SideBar.jsx │ ├── Form.jsx │ └── InstanceBar │ │ └── InstanceBar.jsx ├── assets │ ├── animation-redisee.mp4 │ ├── animation-redisee-dark-mode.mp4 │ ├── logo.svg │ └── logoDarkMode.svg ├── styles.css ├── clockHelperFunction.js ├── index.js ├── redux │ ├── store.js │ ├── persistenceSlice.js │ ├── errorSlice.js │ ├── basicActivitySlice.js │ ├── performanceSlice.js │ ├── memorySlice.js │ └── globalSlice.js ├── graphHelperFunctions.js ├── App.jsx └── index.html ├── server ├── .DS_Store ├── redisClients │ ├── test.js │ └── Instance A.js ├── Routers │ ├── dataRouter.js │ └── connectionRouter.js ├── server.js └── Middleware │ ├── dataMiddleware.js │ └── connectionMiddleware.js ├── coverage └── lcov-report │ ├── favicon.png │ ├── sort-arrow-sprite.png │ ├── prettify.css │ ├── block-navigation.js │ ├── client │ ├── components │ │ ├── StyledComponents │ │ │ ├── variables.js.html │ │ │ └── Themes.js.html │ │ ├── InstanceBar │ │ │ └── index.html │ │ ├── Persistence │ │ │ └── index.html │ │ ├── Errors │ │ │ └── index.html │ │ ├── Toggler.jsx.html │ │ ├── Performance │ │ │ └── index.html │ │ ├── BasicActivities │ │ │ └── index.html │ │ ├── Memory │ │ │ └── index.html │ │ └── HomePage.jsx.html │ ├── redux │ │ ├── store.js.html │ │ └── submitFormSlice.js.html │ ├── index.js.html │ └── clockHelperFunction.js.html │ ├── base.css │ └── sorter.js ├── jest.setup.js ├── babel.config.js ├── test └── App.test.jsx ├── jest.config.js ├── __test__ └── App.test.js ├── testing-utils └── renderWithProvider.js ├── package.json ├── webpack.config.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/* -------------------------------------------------------------------------------- /__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RediseeLabs/redisee/HEAD/.DS_Store -------------------------------------------------------------------------------- /dump.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RediseeLabs/redisee/HEAD/dump.rdb -------------------------------------------------------------------------------- /client/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RediseeLabs/redisee/HEAD/client/.DS_Store -------------------------------------------------------------------------------- /server/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RediseeLabs/redisee/HEAD/server/.DS_Store -------------------------------------------------------------------------------- /client/components/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RediseeLabs/redisee/HEAD/client/components/.DS_Store -------------------------------------------------------------------------------- /coverage/lcov-report/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RediseeLabs/redisee/HEAD/coverage/lcov-report/favicon.png -------------------------------------------------------------------------------- /client/assets/animation-redisee.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RediseeLabs/redisee/HEAD/client/assets/animation-redisee.mp4 -------------------------------------------------------------------------------- /client/components/StyledComponents/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RediseeLabs/redisee/HEAD/client/components/StyledComponents/.DS_Store -------------------------------------------------------------------------------- /coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RediseeLabs/redisee/HEAD/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /client/assets/animation-redisee-dark-mode.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RediseeLabs/redisee/HEAD/client/assets/animation-redisee-dark-mode.mp4 -------------------------------------------------------------------------------- /server/redisClients/test.js: -------------------------------------------------------------------------------- 1 | var redis = require("redis"), 2 | client = redis.createClient('6379', '127.0.0.1'); 3 | client.connect() 4 | module.exports = client; -------------------------------------------------------------------------------- /server/redisClients/Instance A.js: -------------------------------------------------------------------------------- 1 | var redis = require("redis"), 2 | client = redis.createClient('6379', '127.0.0.1'); 3 | client.connect() 4 | module.exports = client; -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | 3 | import '@testing-library/jest-dom'; 4 | import fetchMock from 'jest-fetch-mock'; 5 | 6 | fetchMock.enableMocks(); 7 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-react', 4 | [ 5 | '@babel/preset-env', 6 | { 7 | targets: { 8 | node: 'current', 9 | }, 10 | }, 11 | ], 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /client/styles.css: -------------------------------------------------------------------------------- 1 | html{ 2 | height: auto; 3 | } 4 | 5 | body { 6 | height: auto; 7 | font-family: 'Montserrat', sans-serif; 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | .pie-row .pie-wrap > div { 13 | background: red; 14 | margin: 0 auto; 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /client/components/StyledComponents/variables.js: -------------------------------------------------------------------------------- 1 | // text 2 | export const primaryBlue = '#3861ed'; 3 | export const secondaryBlue = '#ebeffd'; 4 | export const primaryGrey = '#6f78ad'; 5 | export const secondaryGrey = '#9ba4d1'; 6 | export const primaryRed = '#dc143c'; 7 | export const secondaryRed = '#ff0000'; 8 | -------------------------------------------------------------------------------- /test/App.test.jsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import React from 'react'; 3 | import App from '../client/App.jsx'; 4 | 5 | describe('App tests', () => { 6 | it('should contains the heading 1', () => { 7 | render(); 8 | const heading = screen.getByText('/Hello world! I am using React/i'); 9 | expect(heading).toBeInTheDocument() 10 | }); 11 | }); -------------------------------------------------------------------------------- /client/components/StyledComponents/Themes.js: -------------------------------------------------------------------------------- 1 | export const lightTheme = { 2 | primary: '#fff', 3 | secondary: '#ebf0fe', 4 | textPrimary: '#6f759e', 5 | textSecondary: '#a7abcf', 6 | highlight: '#5174ed', 7 | }; 8 | 9 | export const darkTheme = { 10 | primary: '#1a1c1e', 11 | secondary: '#2f3234', 12 | textPrimary: '#adb0b6', 13 | textSecondary: '#6b6e70', 14 | highlight: '#f7f9f9', 15 | }; 16 | -------------------------------------------------------------------------------- /client/clockHelperFunction.js: -------------------------------------------------------------------------------- 1 | export const clock = (duration) => { 2 | // Hours, minutes and seconds 3 | const hrs = Math.floor(duration / 3600); 4 | const mins = Math.floor((duration % 3600) / 60); 5 | const secs = Math.floor(duration % 60); 6 | 7 | let clock = ''; 8 | 9 | if (hrs > 0) { 10 | clock += '' + hrs + ':' + (mins < 10 ? '0' : ''); 11 | } 12 | clock += '' + mins + ':' + (secs < 10 ? '0' : ''); 13 | clock += '' + secs; 14 | return clock; 15 | }; 16 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import App from './App.jsx'; 4 | import './styles.css'; 5 | import { store } from './redux/store.js'; 6 | import { Provider } from 'react-redux'; 7 | import { BrowserRouter } from 'react-router-dom'; 8 | 9 | const container = document.getElementById('app'); 10 | const root = createRoot(container); 11 | root.render( 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverage: false, 3 | collectCoverageFrom: ['client/**/*.{js,jsx}'], 4 | coverageDirectory: 'coverage', 5 | testEnvironment: 'jsdom', 6 | setupFilesAfterEnv: ['/jest.setup.js'], 7 | transform: { 8 | '\\.[jt]sx?$': 'babel-jest', 9 | }, 10 | moduleNameMapper: { 11 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 12 | '/__mocks__/fileMock.js', 13 | '\\.(css|less)$': 'identity-obj-proxy', 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /client/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import performance from './performanceSlice'; 3 | import memory from './memorySlice'; 4 | import basicActivity from './basicActivitySlice'; 5 | import persistence from './persistenceSlice'; 6 | import error from './errorSlice'; 7 | import global from './globalSlice'; 8 | 9 | export const store = configureStore({ 10 | reducer: { 11 | global, 12 | performance, 13 | memory, 14 | basicActivity, 15 | persistence, 16 | error, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /__test__/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import '@testing-library/jest-dom'; 3 | import React from 'react'; 4 | import App from '../client/App.jsx'; 5 | import { renderWithProviders } from '../testing-utils/renderWithProvider.js'; 6 | 7 | describe('App render correctly', () => { 8 | test('RediSee text under logo successfully render', () => { 9 | renderWithProviders(); 10 | const logoElement = screen.getByText('RediSee'); 11 | expect(logoElement).toBeInTheDocument(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /client/components/Toggler.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DarkModeToggler } from './StyledComponents/SideBar.js'; 3 | import { themeToggle } from '../redux/globalSlice'; 4 | import { useDispatch, useSelector } from 'react-redux'; 5 | 6 | const Toggler = (props) => { 7 | const dispatch = useDispatch(); 8 | const theme = useSelector((state) => state.global.theme); 9 | return ( 10 | 11 | 19 | 20 | ); 21 | }; 22 | 23 | export default Toggler; 24 | -------------------------------------------------------------------------------- /client/components/LoadingGraphPage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | GraphGrid, 4 | GraphBox, 5 | MockGraph, 6 | MockTitle, 7 | } from './StyledComponents/GraphGrid'; 8 | 9 | const LoadingGraphPage = () => { 10 | return ( 11 | <> 12 | { 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | } 32 | 33 | ); 34 | }; 35 | 36 | export default LoadingGraphPage; 37 | -------------------------------------------------------------------------------- /server/Routers/dataRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const dataRouter = express.Router(); 3 | const dataMiddleware = require("../Middleware/dataMiddleware"); 4 | 5 | /* - serve all endpoints related to data */ 6 | 7 | 8 | dataRouter.get("/:redisName/performance", dataMiddleware.performance, (req, res) => { 9 | res.status(200).json(res.locals.performance); 10 | }); 11 | 12 | dataRouter.get("/:redisName/memory", dataMiddleware.memory, (req, res) => { 13 | res.status(200).json(res.locals.memory); 14 | }); 15 | 16 | dataRouter.get("/:redisName/basicActivity", dataMiddleware.basicActivity, (req, res) => { 17 | res.status(200).json(res.locals.basicActivity); 18 | }); 19 | 20 | dataRouter.get("/:redisName/persistence", dataMiddleware.persistence, (req, res) => { 21 | res.status(200).json(res.locals.persistence); 22 | }); 23 | 24 | dataRouter.get("/:redisName/error", dataMiddleware.error, (req, res) => { 25 | res.status(200).json(res.locals.error); 26 | }); 27 | 28 | module.exports = dataRouter; 29 | -------------------------------------------------------------------------------- /client/components/StyledComponents/HomePage.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { Button } from './GlobalStyle'; 3 | 4 | export const Page = styled.div` 5 | box-sizing: border-box; 6 | padding-left: 300px; 7 | display: grid; 8 | grid-template-columns: 1fr 1fr; 9 | width: 100vw; 10 | height: 100vh; 11 | gap: 60px; 12 | .container { 13 | display: flex; 14 | flex-direction: column; 15 | align-items: flex-start; 16 | justify-content: center; 17 | margin-left: 50px; 18 | width: 80%; 19 | } 20 | .title { 21 | color: ${({ theme }) => theme.highlight}; 22 | font-size: 3em; 23 | margin-bottom: 0; 24 | } 25 | .subtitle { 26 | color: ${({ theme }) => theme.textPrimary}; 27 | } 28 | img { 29 | margin-top: 40px; 30 | width: 90%; 31 | } 32 | `; 33 | 34 | export const HomeButton = styled(Button)` 35 | width: 150px; 36 | background-color: ${({ theme }) => theme.highlight}; 37 | color: ${({ theme }) => theme.primary}; 38 | display: flex; 39 | justify-content: center; 40 | `; 41 | -------------------------------------------------------------------------------- /client/components/StyledComponents/MessageModal.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const backgroundRed = '#fad1e0'; 4 | const backgroundBlue = '#caf0f8'; 5 | const backgroundOrange = '#ffe6d2'; 6 | 7 | const textRed = '#7c183d'; 8 | const textBlue = '#02045e'; 9 | const textOrange = '#ff8c36'; 10 | 11 | export const Message = styled.div` 12 | display: flex; 13 | align-items: center; 14 | justify-content: space-between; 15 | position: absolute; 16 | top: 5px; 17 | left: 50%; 18 | transform: translateX(-50%); 19 | min-height: 70px; 20 | width: 500px; 21 | background-color: ${(props) => { 22 | if (props.warning) return backgroundOrange; 23 | if (props.error) return backgroundRed; 24 | if (props.succeed) return backgroundBlue; 25 | }}; 26 | color: ${(props) => { 27 | if (props.warning) return textOrange; 28 | if (props.error) return textRed; 29 | if (props.succeed) return textBlue; 30 | }}; 31 | z-index: 3; 32 | border-radius: 5px; 33 | svg { 34 | height: 90%; 35 | margin-right: 15px; 36 | margin-left: 20px; 37 | } 38 | `; 39 | -------------------------------------------------------------------------------- /client/components/MessageModal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Message } from './StyledComponents/MessageModal'; 3 | import { useSelector, useDispatch } from 'react-redux'; 4 | 5 | import { clearMessage } from '../redux/globalSlice'; 6 | import { 7 | UilCheckCircle, 8 | UilExclamationTriangle, 9 | UilExclamationOctagon, 10 | UilMultiply, 11 | } from '@iconscout/react-unicons'; 12 | 13 | const MessageModal = () => { 14 | let message = useSelector((state) => state.global.message); 15 | const dispatch = useDispatch(); 16 | return ( 17 | 22 | {message.type === 'succeed' && } 23 | {message.type === 'warning' && } 24 | {message.type === 'error' && } 25 |

{message.content}

26 | dispatch(clearMessage())} /> 27 |
28 | ); 29 | }; 30 | 31 | export default MessageModal; 32 | -------------------------------------------------------------------------------- /client/components/StyledComponents/GraphGrid.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'styled-components'; 2 | 3 | const fadeIn = keyframes` 4 | from { 5 | opacity: 0; 6 | } 7 | to { 8 | opacity: 1; 9 | } 10 | `; 11 | 12 | export const GraphGrid = styled.div` 13 | display: grid; 14 | grid-template-columns: 50% 50%; 15 | padding: 35px; 16 | height: 100%; 17 | margin: 0px 30px 20px 300px; 18 | grid-gap: 30px; 19 | box-sizing: border-box; 20 | @media (max-width: 1400px) { 21 | grid-template-columns: 100%; 22 | } 23 | `; 24 | 25 | export const GraphBox = styled.div` 26 | display: flex; 27 | flex-direction: column; 28 | justify-content: flex-start; 29 | align-items: center; 30 | `; 31 | 32 | export const MockGraph = styled.div` 33 | background-color: ${({ theme }) => theme.secondary}; 34 | height: 300px; 35 | width: 100%; 36 | animation: ${fadeIn} 0.5s ease-in-out; 37 | `; 38 | export const MockTitle = styled.div` 39 | background-color: ${({ theme }) => theme.secondary}; 40 | height: 40px; 41 | width: 160px; 42 | margin-bottom: 10px; 43 | animation: ${fadeIn} 1s ease-in-out; 44 | `; 45 | -------------------------------------------------------------------------------- /server/Routers/connectionRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const connectionRouter = express.Router(); 3 | const connectionMiddleware = require('../Middleware/connectionMiddleware'); 4 | 5 | /* - connection router handle all endpoints related to clients, such as delelete, add, and create */ 6 | 7 | connectionRouter.post( 8 | '/', 9 | connectionMiddleware.validate, 10 | connectionMiddleware.connect, 11 | (req, res) => { 12 | res.status(200).json('Successfully connected to redis database'); 13 | } 14 | ); 15 | 16 | connectionRouter.get('/', connectionMiddleware.getInstances, (req, res) => { 17 | res.status(200).json(res.locals.instancesArr); 18 | }); 19 | 20 | connectionRouter.delete( 21 | '/deleteOne/:redisName', 22 | connectionMiddleware.disconnectOne, 23 | (req, res) => { 24 | res.status(200).json('Client successfully deleted'); 25 | } 26 | ); 27 | 28 | connectionRouter.delete( 29 | '/deleteMany', 30 | connectionMiddleware.disconnectMany, 31 | (req, res) => { 32 | res.status(200).json('All clients successfully deleted '); 33 | } 34 | ); 35 | 36 | module.exports = connectionRouter; 37 | -------------------------------------------------------------------------------- /client/components/BasicActivities/Keyspace.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | import { GraphBox } from '../StyledComponents/GraphGrid'; 4 | 5 | import { 6 | BarChart, 7 | Bar, 8 | XAxis, 9 | YAxis, 10 | CartesianGrid, 11 | Tooltip, 12 | Legend, 13 | } from 'recharts'; 14 | 15 | /* - graph component: displays only the graph 16 | - it gets the data array from the redux store every 17 | time it changes, and renders on the graphs 18 | */ 19 | 20 | export default function Latency() { 21 | const keyspace = useSelector((state) => state.basicActivity.keyspace); 22 | return ( 23 | 24 |

Keyspaces

25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const dataRouter = require('./Routers/dataRouter.js'); 4 | const connectionRouter = require('./Routers/connectionRouter.js'); 5 | 6 | const app = express(); 7 | const PORT = 3000; 8 | 9 | const corsOptions = { 10 | origin: '*', 11 | optionsSuccessStatus: 200, 12 | }; 13 | 14 | app.use(cors(corsOptions)); 15 | 16 | app.use(express.json()); 17 | app.use(express.urlencoded({ extended: true })); 18 | 19 | /* - routers */ 20 | 21 | app.use('/', dataRouter); 22 | app.use('/connection', connectionRouter); 23 | 24 | app.use('*', (req, res) => { 25 | res.status(500).json('Wrong route'); 26 | }); 27 | 28 | /* - global error handler */ 29 | 30 | app.use((err, req, res, next) => { 31 | const defaultErr = { 32 | log: 'Express error handler caught unknown middleware error', 33 | status: err.status || 500, 34 | message: { err: 'An error occurred' }, 35 | }; 36 | let errorObj = { ...defaultErr, ...err }; 37 | res.status(errorObj.status).json(errorObj.message.err); 38 | }); 39 | 40 | app.listen(PORT, () => { 41 | console.log(`Server listening on port: ${PORT}`); 42 | }); 43 | -------------------------------------------------------------------------------- /client/components/Persistence/Rlst.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { GraphBox } from '../StyledComponents/GraphGrid'; 3 | import { useSelector } from 'react-redux'; 4 | 5 | import { 6 | BarChart, 7 | Bar, 8 | XAxis, 9 | YAxis, 10 | CartesianGrid, 11 | Tooltip, 12 | Legend, 13 | ResponsiveContainer, 14 | } from 'recharts'; 15 | 16 | /* - graph component: displays only the graph 17 | - it gets the data array from the redux store every 18 | time it changes, and renders on the graphs 19 | */ 20 | 21 | export default function Rlst() { 22 | const rlst = useSelector((state) => state.persistence.rlst); 23 | return ( 24 | 25 |

Rlst

26 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /client/components/Persistence/Rcslt.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { GraphBox } from '../StyledComponents/GraphGrid'; 3 | import { useSelector } from 'react-redux'; 4 | 5 | import { 6 | BarChart, 7 | Bar, 8 | XAxis, 9 | YAxis, 10 | CartesianGrid, 11 | Tooltip, 12 | Legend, 13 | ResponsiveContainer, 14 | } from 'recharts'; 15 | 16 | /* - graph component: displays only the graph 17 | - it gets the data array from the redux store every 18 | time it changes, and renders on the graphs 19 | */ 20 | 21 | export default function Rcslt() { 22 | const rcslt = useSelector((state) => state.persistence.rcslt); 23 | return ( 24 | 25 |

Rcslt

26 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /client/components/Performance/Latency.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { GraphBox } from '../StyledComponents/GraphGrid'; 3 | import { useSelector } from 'react-redux'; 4 | 5 | import { 6 | BarChart, 7 | Bar, 8 | XAxis, 9 | YAxis, 10 | CartesianGrid, 11 | Tooltip, 12 | Legend, 13 | ResponsiveContainer, 14 | } from 'recharts'; 15 | 16 | /* - graph component: displays only the graph 17 | - it gets the data array from the redux store every 18 | time it changes, and renders on the graphs 19 | */ 20 | 21 | export default function Latency() { 22 | const latency = useSelector((state) => state.performance.latency); 23 | return ( 24 | 25 |

Latency

26 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /client/components/StyledComponents/GlobalStyle.js: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components'; 2 | import styled from 'styled-components'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | export const GlobalStyle = createGlobalStyle` 6 | body { 7 | background: ${({ theme }) => theme.primary}; 8 | color: ${({ theme }) => theme.textPrimary}; 9 | transition: all 0.4s linear; 10 | height: auto; 11 | font-family: 'Montserrat', sans-serif; 12 | margin: 0; 13 | padding: 0; 14 | } 15 | `; 16 | 17 | export const Button = styled(Link)` 18 | display: flex; 19 | align-items: center; 20 | width: 90%; 21 | border-radius: 10px; 22 | padding: 12px; 23 | font-size: 1.1em; 24 | color: ${(props) => 25 | props.$active ? props.theme.highlight : props.theme.textPrimary}; 26 | font-weight: 500; 27 | text-decoration: none; 28 | margin: 10px 0; 29 | transition: 0.2s ease-out; 30 | background-color: ${(props) => 31 | props.$active ? props.theme.secondary : 'none'}; 32 | &:hover { 33 | background-color: ${({ theme }) => theme.secondary}; 34 | color: ${({ theme }) => theme.highlight}; 35 | } 36 | &:active { 37 | background-color: ${({ theme }) => theme.highlight}; 38 | color: white; 39 | } 40 | `; 41 | -------------------------------------------------------------------------------- /client/components/Errors/keyspaceMisses.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import { GraphBox } from '../StyledComponents/GraphGrid'; 4 | import { 5 | LineChart, 6 | Line, 7 | XAxis, 8 | YAxis, 9 | CartesianGrid, 10 | Tooltip, 11 | Legend, 12 | } from 'recharts'; 13 | 14 | /* - graph component: displays only the graph 15 | - it gets the data array from the redux store every 16 | time it changes, and renders on the graphs 17 | */ 18 | 19 | export default function KeyspaceMisses() { 20 | const keyspaceMisses = useSelector((state) => state.error.keyspace_missed); 21 | return ( 22 | 23 |

Keyspace Misses

24 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /client/components/Performance/HitRate.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | import { GraphBox } from '../StyledComponents/GraphGrid'; 4 | import { ResponsiveContainer, PieChart, Pie } from 'recharts'; 5 | 6 | /* - graph component: displays only the graph 7 | - it gets the data array from the redux store every 8 | time it changes, and renders on the graphs 9 | */ 10 | 11 | export default function App() { 12 | const hitRate = useSelector((state) => state.performance.hitRate); 13 | const ratio = useSelector((state) => state.performance.ratio); 14 | const displayratio = Number(ratio).toFixed(1); 15 | 16 | return ( 17 | 18 |

Hit/Rate Ratio

19 | 20 | 21 | 30 | 31 | 32 |
{`Hit/Miss`}
33 |
{`ratio is ${displayratio}%`}
34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /client/components/Errors/RejectedConnections.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import { GraphBox } from '../StyledComponents/GraphGrid'; 4 | import { 5 | LineChart, 6 | Line, 7 | XAxis, 8 | YAxis, 9 | CartesianGrid, 10 | Tooltip, 11 | Legend, 12 | } from 'recharts'; 13 | 14 | /* - graph component: displays only the graph 15 | - it gets the data array from the redux store every 16 | time it changes, and renders on the graphs 17 | */ 18 | 19 | export default function RejectedConnections() { 20 | const rejectedConnections = useSelector( 21 | (state) => state.error.rejected_connections 22 | ); 23 | return ( 24 | 25 |

Rejected Connections

26 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /client/components/StyledComponents/Form.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { Button } from './GlobalStyle'; 3 | 4 | export const FormModal = styled.div` 5 | width: 100%; 6 | height: 100%; 7 | position: fixed; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | left: 50%; 12 | top: 50%; 13 | transform: translate(-50%, -50%); 14 | background-color: rgb(0, 0, 0, 0.1); 15 | z-index: 2; 16 | form { 17 | max-width: 45%; 18 | max-height: 55%; 19 | background-color: white; 20 | display: flex; 21 | flex-direction: column; 22 | justify-content: center; 23 | align-items: center; 24 | padding: 30px; 25 | box-sizing: border-box; 26 | border-radius: 8px; 27 | } 28 | `; 29 | 30 | export const Input = styled.input` 31 | border-radius: 8px; 32 | height: 30px; 33 | width: 60%; 34 | 35 | border: ${({ theme }) => `1px solid ${theme.secondary}`}; 36 | &:focus { 37 | border: ${({ theme }) => `1px solid ${theme.highlight}`}; 38 | } 39 | `; 40 | 41 | export const SubmitBtn = styled(Button)` 42 | display: flex; 43 | justify-content: center; 44 | align-items: center; 45 | width: 80px; 46 | background-color: ${({ theme }) => theme.secondary}; 47 | :hover { 48 | background-color: ${({ theme }) => theme.highlight}; 49 | color: white; 50 | } 51 | `; 52 | -------------------------------------------------------------------------------- /client/components/Persistence/PersistencePage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import { fectchPersistence } from '../../redux/persistenceSlice'; 4 | import Rlst from './Rlst'; 5 | import Rcslt from './Rcslt'; 6 | import { GraphGrid } from '../StyledComponents/GraphGrid'; 7 | import LoadingGraphPage from '../LoadingGraphPage'; 8 | 9 | /* - when mounted, this component will trigger server 10 | call every second and store new data in persistence redux store 11 | - when loading displays skeleton page 12 | */ 13 | 14 | const PersistencePage = () => { 15 | const dispatch = useDispatch(); 16 | const loading = useSelector((state) => state.persistence.loading); 17 | const selectClient = useSelector((state) => state.global.selectClient); 18 | 19 | const api = `http://localhost:3000/${selectClient}/persistence`; 20 | useEffect(() => { 21 | const interval = setInterval(() => { 22 | dispatch(fectchPersistence(api)); 23 | }, 1000); 24 | return () => clearInterval(interval); 25 | }, []); 26 | 27 | return ( 28 | <> 29 | {loading ? ( 30 | 31 | ) : ( 32 | 33 | 34 | 35 | 36 | )} 37 | 38 | ); 39 | }; 40 | 41 | export default PersistencePage; 42 | -------------------------------------------------------------------------------- /client/components/Errors/ErrorsPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import KeyspaceMisses from './KeyspaceMisses'; 3 | import RejectedConnections from './RejectedConnections'; 4 | import { useSelector, useDispatch } from 'react-redux'; 5 | import { errorFetch } from '../../redux/errorSlice'; 6 | import { GraphGrid } from '../StyledComponents/GraphGrid'; 7 | import LoadingGraphPage from '../LoadingGraphPage'; 8 | 9 | /* - when mounted, this component will trigger server 10 | call every second and store new data in error redux store 11 | - when loading displays skeleton page 12 | */ 13 | 14 | const ErrorsPage = (props) => { 15 | const dispatch = useDispatch(); 16 | const loading = useSelector((state) => state.error.loading); 17 | const selectClient = useSelector((state) => state.global.selectClient); 18 | 19 | const api = `http://localhost:3000/${selectClient}/error`; 20 | useEffect(() => { 21 | const interval = setInterval(() => { 22 | dispatch(errorFetch(api)); 23 | }, 1000); 24 | return () => clearInterval(interval); 25 | }, []); 26 | 27 | return ( 28 | <> 29 | {loading ? ( 30 | 31 | ) : ( 32 | 33 | 34 | 35 | 36 | )} 37 | 38 | ); 39 | }; 40 | 41 | export default ErrorsPage; 42 | -------------------------------------------------------------------------------- /client/components/HomePage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from 'react'; 2 | import { Page, HomeButton } from './StyledComponents/HomePage'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { showForm, closeForm } from '../redux/globalSlice'; 5 | import animation from '../assets/animation-redisee.mp4'; 6 | import animationDark from '../assets/animation-redisee-dark-mode.mp4'; 7 | 8 | const HomePage = (prop) => { 9 | const dispatch = useDispatch(); 10 | const theme = useSelector((state) => state.global.theme); 11 | return ( 12 | { 14 | if (e.key === 'Escape') { 15 | dispatch(closeForm()); 16 | } 17 | }} 18 | > 19 |
20 |

Welcome to RediSee

21 |

22 | Get live data from your redis databe with this lightweight monitoring 23 | tool 24 |

25 | dispatch(showForm())}> 26 | Connect now 27 | 28 |
29 | 30 | 37 |
38 | ); 39 | }; 40 | 41 | export default HomePage; 42 | -------------------------------------------------------------------------------- /client/components/BasicActivities/ConnectedSlaves.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { GraphBox } from '../StyledComponents/GraphGrid'; 3 | import { useSelector } from 'react-redux'; 4 | 5 | import { 6 | LineChart, 7 | Line, 8 | XAxis, 9 | YAxis, 10 | CartesianGrid, 11 | Tooltip, 12 | Legend, 13 | } from 'recharts'; 14 | 15 | /* - graph component: displays only the graph 16 | - it gets the data array from the redux store every 17 | time it changes, and renders on the graphs 18 | */ 19 | 20 | export default function connectedSlaves() { 21 | const connectedSlaves = useSelector( 22 | (state) => state.basicActivity.connected_slaves 23 | ); 24 | return ( 25 | 26 |

Connected slaves

27 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 51 |
52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /client/components/Performance/PerformancePage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import { fetchPerformanceData } from '../../redux/performanceSlice'; 4 | import Latency from './Latency'; 5 | import Iops from './Iops'; 6 | import HitRate from './HitRate'; 7 | import { GraphGrid } from '../StyledComponents/GraphGrid'; 8 | import LoadingGraphPage from '../LoadingGraphPage'; 9 | 10 | /* - when mounted, this component will trigger server 11 | call every second and store new data in performance redux store 12 | - when loading displays skeleton page 13 | */ 14 | 15 | const PerformancePage = () => { 16 | const dispatch = useDispatch(); 17 | 18 | const loading = useSelector((state) => state.performance.loading); 19 | const selectClient = useSelector((state) => state.global.selectClient); 20 | 21 | const api = `http://localhost:3000/${selectClient}/performance`; 22 | useEffect(() => { 23 | const interval = setInterval(() => { 24 | dispatch(fetchPerformanceData(api)); 25 | }, 1000); 26 | return () => clearInterval(interval); 27 | }, []); 28 | 29 | return ( 30 | <> 31 | {loading ? ( 32 | 33 | ) : ( 34 | 35 | 36 | 37 | 38 | 39 | )} 40 | 41 | ); 42 | }; 43 | 44 | export default PerformancePage; 45 | -------------------------------------------------------------------------------- /client/components/BasicActivities/ConnectedClient.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { GraphBox } from '../StyledComponents/GraphGrid'; 3 | import { useSelector } from 'react-redux'; 4 | 5 | import { 6 | LineChart, 7 | Line, 8 | XAxis, 9 | YAxis, 10 | CartesianGrid, 11 | Tooltip, 12 | Legend, 13 | } from 'recharts'; 14 | 15 | /* - graph component: displays only the graph 16 | - it gets the data array from the redux store every 17 | time it changes, and renders on the graphs 18 | */ 19 | 20 | export default function connectedClients() { 21 | const connectedClient = useSelector( 22 | (state) => state.basicActivity.connected_clients 23 | ); 24 | return ( 25 | 26 |

Connected Client

27 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 51 |
52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /testing-utils/renderWithProvider.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { configureStore } from '@reduxjs/toolkit'; 4 | import { Provider } from 'react-redux'; 5 | import performance from '../client/redux/performanceSlice'; 6 | import memory from '../client/redux/memorySlice'; 7 | import persistence from '../client/redux/persistenceSlice'; 8 | import basicActivity from '../client/redux/basicActivitySlice'; 9 | import error from '../client/redux/errorSlice'; 10 | import global from '../client/redux/globalSlice'; 11 | import { BrowserRouter } from 'react-router-dom'; 12 | 13 | export function renderWithProviders( 14 | ui, 15 | { 16 | route = '/', 17 | preloadedState = {}, 18 | // Automatically create a store instance if no store was passed in 19 | store = configureStore({ 20 | reducer: { 21 | performance, 22 | memory, 23 | persistence, 24 | basicActivity, 25 | error, 26 | global, 27 | }, 28 | preloadedState, 29 | }), 30 | ...renderOptions 31 | } = {} 32 | ) { 33 | function Wrapper({ children }) { 34 | return ( 35 | 36 | {children} 37 | 38 | ); 39 | } 40 | 41 | // Return an object with the store and all of RTL's query functions 42 | return { 43 | store, 44 | ...render(ui, { wrapper: Wrapper, ...renderOptions }), 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /client/graphHelperFunctions.js: -------------------------------------------------------------------------------- 1 | /* - helper function that changes data in the redux store to be read by the graph 2 | - input: graphArr: array of objects that will be from the redux store 3 | XaxisName: will be whats displayed on x axis 4 | value: data that we will get from server call 5 | graphName: name the value so recharts can use it to display it 6 | - output: no output, it mutates the store by reference 7 | */ 8 | 9 | export const fillGraph = (graphArr, XaxisName, value, graphName) => { 10 | /* - flag that we switch to true if graph array is not full */ 11 | let flag = false; 12 | for (let i = 0; i < graphArr.length; i++) { 13 | /* - if element is empty, replace it with data object */ 14 | if (JSON.stringify(graphArr[i]) === '{}') { 15 | graphArr[i] = { time: XaxisName, [graphName]: value ? value : 0 }; 16 | flag = true; 17 | break; 18 | } 19 | } 20 | /* - if array is full, shift the oldest data object and push in the incoming data */ 21 | if (!flag) { 22 | graphArr.shift(); 23 | graphArr.push({ time: XaxisName, [graphName]: value ? value : 0 }); 24 | } 25 | }; 26 | /* - same kind of function but with object formatted for pie graph */ 27 | export const pieGraph = (pieArr, data) => { 28 | pieArr[i] = { 29 | name: keySpace, 30 | value: { 31 | hits: data.keyspace_hits, 32 | misses: data.keyspace_misses, 33 | ratio: data.ratio, 34 | }, 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /client/components/Memory/MemoryPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import EvictedKeys from './EvictedKeys'; 3 | import UsedMemoryGraph from './UsedMemoryGraph'; 4 | import FragRatioGraph from './FragRatioGraph'; 5 | import { useSelector, useDispatch } from 'react-redux'; 6 | import { fetchData } from '../../redux/memorySlice'; 7 | import { GraphGrid } from '../StyledComponents/GraphGrid'; 8 | import LoadingGraphPage from '../LoadingGraphPage'; 9 | 10 | /* - when mounted, this component will trigger server 11 | call every second and store new data in memory redux store 12 | - when loading displays skeleton page 13 | */ 14 | 15 | const MemoryPage = (props) => { 16 | const dispatch = useDispatch(); 17 | const loading = useSelector((state) => state.memory.loading); 18 | const selectClient = useSelector((state) => state.global.selectClient); 19 | const clients = useSelector((state) => state.global.clients); 20 | 21 | const api = `http://localhost:3000/${selectClient}/memory`; 22 | useEffect(() => { 23 | const interval = setInterval(() => { 24 | dispatch(fetchData(api)); 25 | }, 1000); 26 | return () => { 27 | clearInterval(interval); 28 | }; 29 | }, []); 30 | 31 | if (loading) return ; 32 | else 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | export default MemoryPage; 43 | -------------------------------------------------------------------------------- /client/components/BasicActivities/ActivitiesPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import { fetchBasicActivity } from '../../redux/basicActivitySlice'; 4 | import ConnectedClient from './ConnectedClient'; 5 | import ConnectedSlaves from './ConnectedSlaves'; 6 | import Keyspace from './Keyspace'; 7 | import { GraphGrid } from '../StyledComponents/GraphGrid'; 8 | import LoadingGraphPage from '../LoadingGraphPage'; 9 | 10 | /* - when mounted, this component will trigger server 11 | call every second and store new data in basicActivity redux store 12 | - when loading displays skeleton page 13 | */ 14 | 15 | const ActivitiesPage = () => { 16 | const dispatch = useDispatch(); 17 | 18 | const loading = useSelector((state) => state.basicActivity.loading); 19 | const selectClient = useSelector((state) => state.global.selectClient); 20 | const api = `http://localhost:3000/${selectClient}/basicActivity`; 21 | useEffect(() => { 22 | const activitiesInterval = setInterval(() => { 23 | dispatch(fetchBasicActivity(api)); 24 | }, 1000); 25 | return () => clearInterval(activitiesInterval); 26 | }, []); 27 | 28 | return ( 29 | <> 30 | {loading ? ( 31 | 32 | ) : ( 33 | 34 | 35 | 36 | 37 | 38 | )} 39 | 40 | ); 41 | }; 42 | 43 | export default ActivitiesPage; 44 | -------------------------------------------------------------------------------- /client/components/Performance/Iops.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { GraphBox } from '../StyledComponents/GraphGrid'; 3 | import { useSelector } from 'react-redux'; 4 | 5 | import { 6 | AreaChart, 7 | Area, 8 | XAxis, 9 | YAxis, 10 | CartesianGrid, 11 | Tooltip, 12 | ResponsiveContainer, 13 | } from 'recharts'; 14 | 15 | /* - graph component: displays only the graph 16 | - it gets the data array from the redux store every 17 | time it changes, and renders on the graphs 18 | */ 19 | 20 | export default function Iops() { 21 | const iops = useSelector((state) => state.performance.iops); 22 | return ( 23 | 24 |

Instantaneous ops

25 | 26 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 50 | 51 | 52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /client/components/Memory/UsedMemoryGraph.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { GraphBox } from '../StyledComponents/GraphGrid'; 3 | import { useSelector, useDispatch } from 'react-redux'; 4 | 5 | import { 6 | AreaChart, 7 | Area, 8 | XAxis, 9 | YAxis, 10 | CartesianGrid, 11 | Tooltip, 12 | ResponsiveContainer, 13 | } from 'recharts'; 14 | 15 | /* - graph component: displays only the graph 16 | - it gets the data array from the redux store every 17 | time it changes, and renders on the graphs 18 | */ 19 | 20 | const UsedMemoryGraph = () => { 21 | const usedMemory = useSelector((state) => state.memory.used_memory); 22 | return ( 23 | 24 |

Used Memory

25 | 26 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 50 | 51 | 52 |
53 | ); 54 | }; 55 | 56 | export default UsedMemoryGraph; 57 | -------------------------------------------------------------------------------- /client/components/Memory/EvictedKeys.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import { GraphBox } from '../StyledComponents/GraphGrid'; 4 | import { 5 | AreaChart, 6 | Area, 7 | XAxis, 8 | YAxis, 9 | CartesianGrid, 10 | Tooltip, 11 | ResponsiveContainer, 12 | } from 'recharts'; 13 | 14 | /* - graph component: displays only the graph 15 | - it gets the data array from the redux store every 16 | time it changes, and renders on the graphs 17 | */ 18 | 19 | const EvictedKeys = (props) => { 20 | const evictedKeys = useSelector((state) => state.memory.evicted_keys); 21 | return ( 22 | 23 |

Evicted Keys

24 | 25 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 50 | 51 | 52 |
53 | ); 54 | }; 55 | 56 | export default EvictedKeys; 57 | -------------------------------------------------------------------------------- /client/components/Memory/FragRatioGraph.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { GraphBox } from '../StyledComponents/GraphGrid'; 3 | import { useSelector, useDispatch } from 'react-redux'; 4 | import { 5 | AreaChart, 6 | Area, 7 | XAxis, 8 | YAxis, 9 | CartesianGrid, 10 | Tooltip, 11 | ResponsiveContainer, 12 | } from 'recharts'; 13 | 14 | /* - graph component: displays only the graph 15 | - it gets the data array from the redux store every 16 | time it changes, and renders on the graphs 17 | */ 18 | 19 | const FragRatioGraph = (props) => { 20 | const memFragmentation = useSelector( 21 | (state) => state.memory.mem_fragmentation_ratio 22 | ); 23 | 24 | return ( 25 | 26 |

Memory fragmentation ratio

27 | 28 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 52 | 53 | 54 |
55 | ); 56 | }; 57 | 58 | export default FragRatioGraph; 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redis-see", 3 | "version": "1.0.0", 4 | "description": "OSP Codesmith nyoi2", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "start": "nodemon ./server/server.js", 9 | "build": "webpack --mode production", 10 | "dev": "concurrently \"webpack serve --open --mode development\" \"nodemon ./server/server.js\"" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@iconscout/react-unicons": "^1.1.6", 16 | "@reduxjs/toolkit": "^1.9.2", 17 | "axios": "^1.3.2", 18 | "cors": "^2.8.5", 19 | "express": "^4.18.2", 20 | "iframe-resizer-react": "^1.1.0", 21 | "nodemon": "^2.0.20", 22 | "react": "^18.2.0", 23 | "react-dom": "^18.2.0", 24 | "react-iframe": "^1.8.5", 25 | "react-is": "^18.2.0", 26 | "react-redux": "^8.0.5", 27 | "react-router-dom": "^6.8.1", 28 | "recharts": "^2.4.1", 29 | "redis": "^4.6.4", 30 | "redis-info": "^3.1.0", 31 | "styled-components": "^5.3.6", 32 | "svg-url-loader": "^8.0.0", 33 | "ts-jest": "^29.0.5", 34 | "webpack": "^5.75.0" 35 | }, 36 | "devDependencies": { 37 | "@babel/core": "^7.21.0", 38 | "@babel/preset-env": "^7.20.2", 39 | "@babel/preset-react": "^7.18.6", 40 | "@testing-library/dom": "^9.0.0", 41 | "@testing-library/jest-dom": "^5.16.5", 42 | "@testing-library/react": "^14.0.0", 43 | "babel-jest": "^29.4.3", 44 | "babel-loader": "^9.1.2", 45 | "concurrently": "^7.6.0", 46 | "css-loader": "^6.7.3", 47 | "file-loader": "^6.2.0", 48 | "html-loader": "^4.2.0", 49 | "html-webpack-plugin": "^5.5.0", 50 | "identity-obj-proxy": "^3.0.0", 51 | "jest": "^29.4.3", 52 | "jest-environment-jsdom": "^29.4.3", 53 | "react-is": "^18.2.0", 54 | "style-loader": "^3.3.1", 55 | "webpack": "^5.75.0", 56 | "webpack-cli": "^5.0.1", 57 | "webpack-dev-server": "^4.11.1" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmLWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './client/index.js', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, './build'), 9 | publicPath: '/', 10 | }, 11 | devServer: { 12 | historyApiFallback: true, 13 | static: { 14 | directory: path.resolve(__dirname, './build'), 15 | }, 16 | port: 8080, 17 | // proxy: { 18 | // '/': 'http://localhost:3000', 19 | // }, 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.(jsx|js)$/i, 25 | exclude: /(node_modules|bower_components)/, 26 | use: { 27 | loader: 'babel-loader', 28 | options: { 29 | presets: [ 30 | '@babel/preset-react', 31 | [ 32 | '@babel/preset-env', 33 | { 34 | targets: { 35 | node: 'current', 36 | }, 37 | }, 38 | ], 39 | ], 40 | }, 41 | }, 42 | }, 43 | { 44 | test: /\.css$/, 45 | use: [{ loader: 'style-loader' }, { loader: 'css-loader' }], 46 | }, 47 | { 48 | test: /\.(png|jpg)/, 49 | type: 'asset/resource', 50 | }, 51 | { 52 | test: /\.svg$/, 53 | use: [ 54 | { 55 | loader: 'svg-url-loader', 56 | options: { 57 | limit: 10000, 58 | }, 59 | }, 60 | ], 61 | }, 62 | { 63 | test: /\.(png|jpe?g|gif|mp4)$/i, 64 | use: [ 65 | { 66 | loader: 'file-loader', 67 | }, 68 | ], 69 | }, 70 | ], 71 | }, 72 | plugins: [ 73 | new HtmLWebpackPlugin({ 74 | template: path.join(__dirname, 'client/index.html'), 75 | }), 76 | ], 77 | resolve: { 78 | extensions: ['.jsx', '.js'], 79 | }, 80 | }; 81 | -------------------------------------------------------------------------------- /client/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /client/assets/logoDarkMode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 11 | 12 | 15 | 18 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /client/redux/persistenceSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import axios from 'axios'; 3 | import { fillGraph } from '../graphHelperFunctions'; 4 | import { setMessage } from './globalSlice'; 5 | import { clock } from '../clockHelperFunction'; 6 | 7 | const initialState = { 8 | startedTime: null, 9 | loading: true, 10 | rlst: Array(15).fill({}), 11 | rcslt: Array(15).fill({}), 12 | }; 13 | 14 | export const fectchPersistence = (api) => (dispatch, getState) => { 15 | if (JSON.stringify(getState().persistence.rlst[0]) === '{}') { 16 | dispatch(persistenceSlice.actions.startLoading()); 17 | dispatch(persistenceSlice.actions.setStartTime()); 18 | } 19 | axios 20 | .get(api) 21 | .then((res) => res.data) 22 | .then((data) => { 23 | dispatch(persistenceSlice.actions.addToGraph(data)); 24 | dispatch(persistenceSlice.actions.stopLoading()); 25 | }) 26 | .catch((err) => { 27 | dispatch(setMessage({ type: 'error', content: err.response.data })); 28 | }); 29 | }; 30 | 31 | export const persistenceSlice = createSlice({ 32 | name: 'persistence', 33 | initialState: initialState, 34 | reducers: { 35 | setStartTime: (state, action) => { 36 | state.startedTime = Date.now() / 1000; 37 | }, 38 | startLoading: (state, action) => { 39 | state.loading = true; 40 | }, 41 | stopLoading: (state, action) => { 42 | state.loading = false; 43 | }, 44 | addToGraph: (state, action) => { 45 | fillGraph( 46 | state.rlst, 47 | clock(Date.now() / 1000 - state.startedTime), 48 | action.payload.rlst, 49 | 'rlst' 50 | ); 51 | fillGraph( 52 | state.rcslt, 53 | clock(Date.now() / 1000 - state.startedTime), 54 | action.payload.rcslt, 55 | 'rcslt' 56 | ); 57 | }, 58 | clearState: (state, action) => { 59 | state.loading = true; 60 | state.rlst = Array(15).fill({}); 61 | state.rcslt = Array(15).fill({}); 62 | }, 63 | }, 64 | }); 65 | 66 | export const { startLoading, stopLoading, clearState } = 67 | persistenceSlice.actions; 68 | 69 | export default persistenceSlice.reducer; 70 | -------------------------------------------------------------------------------- /client/redux/errorSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import { fillGraph } from '../graphHelperFunctions'; 3 | import axios from 'axios'; 4 | import { clock } from '../clockHelperFunction'; 5 | 6 | const initialState = { 7 | startedTime: null, 8 | loading: true, 9 | rejected_connections: Array(15).fill({}), 10 | keyspace_missed: Array(15).fill({}), 11 | }; 12 | 13 | let cache; 14 | 15 | export const errorFetch = (api) => (dispatch, getState) => { 16 | if (api !== cache) { 17 | dispatch(errorSlice.actions.clearState()); 18 | } 19 | if (JSON.stringify(getState().error.rejected_connections[0]) === '{}') { 20 | dispatch(errorSlice.actions.setStartTime()); 21 | dispatch(errorSlice.actions.startLoading()); 22 | } 23 | axios 24 | .get(api) 25 | .then((res) => res.data) 26 | .then((data) => { 27 | dispatch(errorSlice.actions.addToGraph(data)); 28 | dispatch(errorSlice.actions.stopLoading()); 29 | }); 30 | cache = api; 31 | }; 32 | 33 | export const errorSlice = createSlice({ 34 | name: 'error', 35 | initialState: initialState, 36 | reducers: { 37 | setStartTime: (state, action) => { 38 | state.startedTime = Date.now() / 1000; 39 | }, 40 | startLoading: (state, action) => { 41 | state.loading = true; 42 | }, 43 | stopLoading: (state, action) => { 44 | state.loading = false; 45 | }, 46 | addToGraph: (state, action) => { 47 | fillGraph( 48 | state.rejected_connections, 49 | clock(Date.now() / 1000 - state.startedTime), 50 | action.payload.rejectedConnection, 51 | 'rejected_connections' 52 | ); 53 | fillGraph( 54 | state.keyspace_missed, 55 | clock(Date.now() / 1000 - state.startedTime), 56 | action.payload.keyspaceMisses, 57 | 'keyspace_misses' 58 | ); 59 | }, 60 | clearState: (state, action) => { 61 | state.loading = true; 62 | state.rejected_connections = Array(15).fill({}); 63 | state.keyspace_missed = Array(15).fill({}); 64 | }, 65 | }, 66 | }); 67 | export const { startLoading, stopLoading, clearState } = errorSlice.actions; 68 | 69 | export default errorSlice.reducer; 70 | -------------------------------------------------------------------------------- /client/components/SideBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Menu, 4 | PerfIcon, 5 | MemoryIcon, 6 | PersistenceIcon, 7 | BasicIcon, 8 | ErrorIcon, 9 | Logo, 10 | SecondaryText, 11 | } from './StyledComponents/SideBar'; 12 | 13 | import { Button } from './StyledComponents/GlobalStyle'; 14 | import logo from '../assets/logo.svg'; 15 | import logoDark from '../assets/logoDarkMode.svg'; 16 | import InstanceBar from './InstanceBar/InstanceBar'; 17 | import { useSelector } from 'react-redux'; 18 | import Toggler from './Toggler'; 19 | 20 | /* - this is the sidebar, it serves as navigation 21 | - it holds the component instance bar that will display all running clients 22 | - plus button adds a new client 23 | - buttons to switch pages: memory, performance, etc... 24 | */ 25 | 26 | const SideBar = (props) => { 27 | const selectClient = useSelector((state) => state.global.selectClient); 28 | const theme = useSelector((state) => state.global.theme); 29 | 30 | return ( 31 | 32 | 33 | 34 |

RediSee

35 |
36 |
37 | 38 | Analytics : 39 | {/* - provide selected client from Redux state to the route so we can data from the selected client */} 40 | 44 | 48 | 52 | 56 | 60 | Setting: 61 | {/* */} 65 | 66 | {/*
Light/Dark
*/} 67 | Light/Dark: 68 | 69 | 70 |
71 |
72 | ); 73 | }; 74 | 75 | export default SideBar; 76 | -------------------------------------------------------------------------------- /client/redux/basicActivitySlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import { fillGraph } from '../graphHelperFunctions'; 3 | import axios from 'axios'; 4 | import { setMessage } from './globalSlice'; 5 | import { clock } from '../clockHelperFunction'; 6 | 7 | const initialState = { 8 | loading: true, 9 | startedTime: null, 10 | connected_clients: Array(15).fill({}), 11 | connected_slaves: Array(15).fill({}), 12 | keyspace: Array(15).fill({}), 13 | }; 14 | 15 | export const fetchBasicActivity = (api) => (dispatch, getState) => { 16 | if (JSON.stringify(getState().basicActivity.connected_clients[0]) === '{}') { 17 | dispatch(basicActivitySlice.actions.startLoading()); 18 | dispatch(basicActivitySlice.actions.setStartTime()); 19 | } 20 | axios 21 | .get(api) 22 | .then((res) => res.data) 23 | .then((data) => { 24 | dispatch(basicActivitySlice.actions.addToGraph(data)); 25 | dispatch(basicActivitySlice.actions.stopLoading()); 26 | }) 27 | .catch((err) => { 28 | dispatch(setMessage({ type: 'error', content: err.response.data })); 29 | }); 30 | }; 31 | 32 | export const basicActivitySlice = createSlice({ 33 | name: 'basicActivity', 34 | initialState: initialState, 35 | reducers: { 36 | setStartTime: (state, action) => { 37 | state.startedTime = Date.now() / 1000; 38 | }, 39 | startLoading: (state, action) => { 40 | state.loading = true; 41 | }, 42 | stopLoading: (state, action) => { 43 | state.loading = false; 44 | }, 45 | addToGraph: (state, action) => { 46 | fillGraph( 47 | state.connected_clients, 48 | clock(Date.now() / 1000 - state.startedTime), 49 | action.payload.connected_clients, 50 | 'connected_clients' 51 | ); 52 | fillGraph( 53 | state.connected_slaves, 54 | clock(Date.now() / 1000 - state.startedTime), 55 | action.payload.connected_slaves, 56 | 'connected_slaves' 57 | ); 58 | fillGraph( 59 | state.keyspace, 60 | clock(Date.now() / 1000 - state.startedTime), 61 | action.payload.keyspace, 62 | 'keyspace' 63 | ); 64 | }, 65 | clearState: (state, action) => { 66 | state.loading = true; 67 | state.connected_clients = Array(15).fill({}); 68 | state.connected_slaves = Array(15).fill({}); 69 | state.keyspace = Array(15).fill({}); 70 | }, 71 | }, 72 | }); 73 | export const { startLoading, stopLoading, clearState } = 74 | basicActivitySlice.actions; 75 | 76 | export default basicActivitySlice.reducer; 77 | -------------------------------------------------------------------------------- /client/components/Form.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useDispatch } from 'react-redux'; 3 | import { closeForm, addOneRedis } from '../redux/globalSlice'; 4 | import { FormModal, Input, SubmitBtn } from './StyledComponents/Form'; 5 | 6 | function Form() { 7 | const [redisName, typeRedisName] = useState(''); 8 | const [port, typePort] = useState(''); 9 | const [host, typeHost] = useState(''); 10 | const dispatch = useDispatch(); 11 | 12 | const redisNameChange = (e) => { 13 | typeRedisName(e.target.value); 14 | }; 15 | const portChange = (e) => { 16 | typePort(e.target.value); 17 | }; 18 | const hostChange = (e) => { 19 | typeHost(e.target.value); 20 | }; 21 | 22 | const handleSubmit = (e) => { 23 | e.preventDefault(); 24 | dispatch( 25 | addOneRedis({ 26 | redisName, 27 | port, 28 | host, 29 | }) 30 | ); 31 | }; 32 | 33 | return ( 34 | dispatch(closeForm())} 36 | onKeyDown={(e) => { 37 | if (e.key === 'Escape') { 38 | dispatch(closeForm()); 39 | } 40 | if (e.key === 'Enter') { 41 | handleSubmit(e); 42 | } 43 | }} 44 | > 45 |
{ 47 | e.stopPropagation(); 48 | }} 49 | onSubmit={handleSubmit} 50 | > 51 |

Connect with your Redis

52 | 53 |
54 | { 60 | redisNameChange(e); 61 | }} 62 | /> 63 |
64 | 65 | 66 |
67 | { 72 | portChange(e); 73 | }} 74 | /> 75 |
76 | 77 |
78 | { 84 | hostChange(e); 85 | }} 86 | /> 87 |
88 | { 90 | handleSubmit(e); 91 | }} 92 | type='submit' 93 | > 94 | Submit 95 | 96 |
97 |
98 | ); 99 | } 100 | 101 | export default Form; 102 | -------------------------------------------------------------------------------- /client/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Routes, Route, redirect, useNavigate } from 'react-router-dom'; 3 | import SideBar from './components/SideBar'; 4 | import ActivitiesPage from './components/BasicActivities/ActivitiesPage'; 5 | import ErrorsPage from './components/Errors/ErrorsPage'; 6 | import MemoryPage from './components/Memory/MemoryPage'; 7 | import PerformancePage from './components/Performance/PerformancePage'; 8 | import PersistencePage from './components/Persistence/PersistencePage'; 9 | import MessageModal from './components/MessageModal'; 10 | import HomePage from './components/HomePage'; 11 | import { clearMessage } from './redux/globalSlice'; 12 | import { ThemeProvider } from 'styled-components'; 13 | import { GlobalStyle } from './components/StyledComponents/GlobalStyle'; 14 | import { lightTheme, darkTheme } from './components/StyledComponents/Themes'; 15 | import { useSelector, useDispatch } from 'react-redux'; 16 | 17 | /* - this is top level component, it displays sidebar and pages 18 | - it uses React Router to render different component for each route 19 | - components will be pages containing graphs 20 | - sidebar will always remain on screen 21 | - if message needs to be displayed, app will render message model component 22 | - used theme provider from style component library to provide styling variable 23 | to all components 24 | */ 25 | 26 | const App = () => { 27 | const theme = useSelector((state) => state.global.theme); 28 | const message = useSelector((state) => state.global.message); 29 | const show = useSelector((state) => state.global.showForm); 30 | 31 | const dispatch = useDispatch(); 32 | const navigate = useNavigate(); 33 | 34 | /* - clears message box after 3s, if status is successful */ 35 | // if its an error redirect to homepage 36 | useEffect(() => { 37 | if (message && message.type === 'succeed') { 38 | setTimeout(() => dispatch(clearMessage()), 2500); 39 | } 40 | if (message && message.type === 'error') { 41 | navigate('/'); 42 | } 43 | }, [message]); 44 | 45 | return ( 46 |
47 | {message && } 48 | 49 | 50 | 51 | 52 | } 55 | /> 56 | } /> 57 | } /> 58 | } /> 59 | } /> 60 | } /> 61 | 62 | 63 |
64 | ); 65 | }; 66 | 67 | export default App; 68 | -------------------------------------------------------------------------------- /coverage/lcov-report/block-navigation.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var jumpToCode = (function init() { 3 | // Classes of code we would like to highlight in the file view 4 | var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; 5 | 6 | // Elements to highlight in the file listing view 7 | var fileListingElements = ['td.pct.low']; 8 | 9 | // We don't want to select elements that are direct descendants of another match 10 | var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` 11 | 12 | // Selecter that finds elements on the page to which we can jump 13 | var selector = 14 | fileListingElements.join(', ') + 15 | ', ' + 16 | notSelector + 17 | missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` 18 | 19 | // The NodeList of matching elements 20 | var missingCoverageElements = document.querySelectorAll(selector); 21 | 22 | var currentIndex; 23 | 24 | function toggleClass(index) { 25 | missingCoverageElements 26 | .item(currentIndex) 27 | .classList.remove('highlighted'); 28 | missingCoverageElements.item(index).classList.add('highlighted'); 29 | } 30 | 31 | function makeCurrent(index) { 32 | toggleClass(index); 33 | currentIndex = index; 34 | missingCoverageElements.item(index).scrollIntoView({ 35 | behavior: 'smooth', 36 | block: 'center', 37 | inline: 'center' 38 | }); 39 | } 40 | 41 | function goToPrevious() { 42 | var nextIndex = 0; 43 | if (typeof currentIndex !== 'number' || currentIndex === 0) { 44 | nextIndex = missingCoverageElements.length - 1; 45 | } else if (missingCoverageElements.length > 1) { 46 | nextIndex = currentIndex - 1; 47 | } 48 | 49 | makeCurrent(nextIndex); 50 | } 51 | 52 | function goToNext() { 53 | var nextIndex = 0; 54 | 55 | if ( 56 | typeof currentIndex === 'number' && 57 | currentIndex < missingCoverageElements.length - 1 58 | ) { 59 | nextIndex = currentIndex + 1; 60 | } 61 | 62 | makeCurrent(nextIndex); 63 | } 64 | 65 | return function jump(event) { 66 | if ( 67 | document.getElementById('fileSearch') === document.activeElement && 68 | document.activeElement != null 69 | ) { 70 | // if we're currently focused on the search input, we don't want to navigate 71 | return; 72 | } 73 | 74 | switch (event.which) { 75 | case 78: // n 76 | case 74: // j 77 | goToNext(); 78 | break; 79 | case 66: // b 80 | case 75: // k 81 | case 80: // p 82 | goToPrevious(); 83 | break; 84 | } 85 | }; 86 | })(); 87 | window.addEventListener('keydown', jumpToCode); 88 | -------------------------------------------------------------------------------- /client/redux/performanceSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import { fillGraph } from '../graphHelperFunctions.js'; 3 | import { setMessage } from './globalSlice.js'; 4 | import axios from 'axios'; 5 | import { clock } from '../clockHelperFunction'; 6 | 7 | const initialState = { 8 | startedTime: null, 9 | loading: true, 10 | latency: Array(15).fill({}), 11 | iops: Array(15).fill({}), 12 | // hitRate: Array(2).fill({}), 13 | hitRate: [ 14 | { name: 'keyspace_hits', value: 0, fill: '#3861ed' }, 15 | { name: 'keyspace_misses', value: 0, fill: '#dc143c' }, 16 | ], 17 | ratio: 0, 18 | ratio: 0, 19 | }; 20 | /* - because reducer doesn't allow async action, we use redux thunk 21 | - redux thunk that make a call to server at memory route and call fetch reducer 22 | - then dispatch add to graph action with returned data 23 | */ 24 | export const fetchPerformanceData = (api) => (dispatch, getState) => { 25 | if (JSON.stringify(getState().performance.latency[0]) === '{}') { 26 | dispatch(performanceSlice.actions.startLoading()); 27 | dispatch(performanceSlice.actions.setStartTime()); 28 | } 29 | axios 30 | .get(api) 31 | .then((res) => res.data) 32 | .then((data) => { 33 | dispatch(performanceSlice.actions.addToGraph(data)); 34 | dispatch(performanceSlice.actions.stopLoading()); 35 | }) 36 | .catch((err) => { 37 | dispatch(setMessage({ type: 'error', content: err.response.data })); 38 | }); 39 | }; 40 | 41 | export const performanceSlice = createSlice({ 42 | name: 'performance', 43 | initialState: initialState, 44 | reducers: { 45 | setStartTime: (state, action) => { 46 | state.startedTime = Date.now() / 1000; 47 | }, 48 | startLoading: (state, action) => { 49 | state.loading = true; 50 | }, 51 | stopLoading: (state, action) => { 52 | state.loading = false; 53 | }, 54 | addToGraph: (state, action) => { 55 | fillGraph( 56 | state.latency, 57 | clock(Date.now() / 1000 - state.startedTime), 58 | action.payload.latency, 59 | 'Live_Redis_latency' 60 | ); 61 | fillGraph( 62 | state.iops, 63 | clock(Date.now() / 1000 - state.startedTime), 64 | action.payload.iops, 65 | 'iops' 66 | ); 67 | state.hitRate[0].value = Number(action.payload.hitRate.keyspace_hits); 68 | state.hitRate[1].value = Number(action.payload.hitRate.keyspace_misses); 69 | state.ratio = Number(action.payload.hitRate.ratio).toFixed(3); 70 | }, 71 | clearState: (state, action) => { 72 | state.loading = true; 73 | state.latency = Array(15).fill({}); 74 | state.iops = Array(15).fill({}); 75 | state.hitRate = [ 76 | { name: 'keyspace_hits', value: 0, fill: '#3861ed' }, 77 | { name: 'keyspace_misses', value: 0, fill: '#dc143c' }, 78 | ]; 79 | state.ratio = 0; 80 | state.ratio = 0; 81 | }, 82 | }, 83 | }); 84 | 85 | export const { startLoading, stopLoading, clearState } = 86 | performanceSlice.actions; 87 | 88 | export default performanceSlice.reducer; 89 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RediSee 5 | 6 | 7 | 11 | 12 | 13 | 74 | 75 | 76 |
77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /client/redux/memorySlice.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux'; 2 | // import { useNavigate } from 'react-router-dom'; 3 | import { createSlice } from '@reduxjs/toolkit'; 4 | import { fillGraph } from '../graphHelperFunctions'; 5 | import { setMessage } from './globalSlice'; 6 | import axios from 'axios'; 7 | import { clock } from '../clockHelperFunction'; 8 | 9 | /* - slice of store that store memory related data and actions */ 10 | 11 | const initialState = { 12 | startedTime: null, 13 | loading: true, 14 | used_memory: Array(15).fill({}), 15 | mem_fragmentation_ratio: Array(15).fill({}), 16 | evicted_keys: Array(15).fill({}), 17 | }; 18 | 19 | /* - because reducer doesn't allow async action, we use redux thunk 20 | - redux thunk that make a call to server at memory route and call fetch reducer 21 | - data expected : { usedMemory : number, memFragmentationRatio: number, evictedKeys: number } 22 | - then dispatch add to graph action with returned data 23 | */ 24 | 25 | export const fetchData = (api) => (dispatch, getState) => { 26 | /* - check if its the first call, if it is change the loading state to be true */ 27 | if (JSON.stringify(getState().memory.used_memory[0]) === '{}') { 28 | dispatch(memorySlice.actions.startLoading()); 29 | dispatch(memorySlice.actions.setStartTime()); 30 | } 31 | axios 32 | .get(api) 33 | .then((res) => res.data) 34 | .then((data) => { 35 | dispatch(memorySlice.actions.addToGraph(data)); 36 | dispatch(memorySlice.actions.stopLoading()); 37 | }) 38 | .catch((err) => { 39 | dispatch(setMessage({ type: 'error', content: err.response.data })); 40 | }); 41 | }; 42 | 43 | export const memorySlice = createSlice({ 44 | name: 'memory', 45 | initialState: initialState, 46 | reducers: { 47 | setStartTime: (state, action) => { 48 | state.startedTime = Date.now() / 1000; 49 | }, 50 | startLoading: (state, action) => { 51 | state.loading = true; 52 | }, 53 | stopLoading: (state, action) => { 54 | state.loading = false; 55 | }, 56 | addToGraph: (state, action) => { 57 | /* - use helper function to change stored data that will be read by the graph */ 58 | fillGraph( 59 | state.used_memory, 60 | clock(Date.now() / 1000 - state.startedTime), 61 | action.payload.usedMemory, 62 | 'used_memory' 63 | ); 64 | fillGraph( 65 | state.mem_fragmentation_ratio, 66 | clock(Date.now() / 1000 - state.startedTime), 67 | action.payload.memFragmentationRatio, 68 | 'mem_fragmentation_ratio' 69 | ); 70 | fillGraph( 71 | state.evicted_keys, 72 | clock(Date.now() / 1000 - state.startedTime), 73 | action.payload.evictedKeys, 74 | 'evicted_keys' 75 | ); 76 | }, 77 | clearState: (state, action) => { 78 | state.loading = true; 79 | state.used_memory = Array(15).fill({}); 80 | state.mem_fragmentation_ratio = Array(15).fill({}); 81 | state.evicted_keys = Array(15).fill({}); 82 | }, 83 | }, 84 | }); 85 | 86 | export const { startLoading, stopLoading, clearState } = memorySlice.actions; 87 | 88 | export default memorySlice.reducer; 89 | -------------------------------------------------------------------------------- /client/components/InstanceBar/InstanceBar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import { 5 | showForm, 6 | selectClient, 7 | deleteOne, 8 | deleteMany, 9 | fetchClients, 10 | } from '../../redux/globalSlice'; 11 | import Form from '../Form'; 12 | import { 13 | DeleteButton, 14 | DeleteIcon, 15 | SecondaryText, 16 | AddButton, 17 | ClearAllButton, 18 | } from '../StyledComponents/SideBar'; 19 | import { Button } from '../StyledComponents/GlobalStyle'; 20 | import { clearState as clearMemory } from '../../redux/memorySlice'; 21 | import { clearState as clearPerformance } from '../../redux/performanceSlice'; 22 | import { clearState as clearPersistence } from '../../redux/persistenceSlice'; 23 | import { clearState as clearBasicActivity } from '../../redux/basicActivitySlice'; 24 | import { clearState as clearError } from '../../redux/errorSlice'; 25 | import { setMessage } from '../../redux/globalSlice'; 26 | import { closeForm } from '../../redux/globalSlice'; 27 | 28 | /* - it displays all clients running 29 | - inside this component we can delete one or clear all instances of clients running 30 | */ 31 | 32 | const InstanceBar = () => { 33 | const dispatch = useDispatch(); 34 | const navigate = useNavigate(); 35 | // - when component mounts fetch all running clients server side 36 | useEffect(() => { 37 | dispatch(fetchClients()); 38 | }, []); 39 | 40 | const instances = useSelector((state) => state.global.clients); 41 | const show = useSelector((state) => state.global.showForm); 42 | const selectedClient = useSelector((state) => state.global.selectClient); 43 | 44 | let allInstance = []; 45 | 46 | for (let i = 0; i < instances.length; i++) { 47 | allInstance.push( 48 |
52 | 77 | { 79 | dispatch(deleteOne(instances[i])); 80 | }} 81 | > 82 | 83 | 84 |
85 | ); 86 | } 87 | 88 | return ( 89 |
{ 91 | if (e.key === 'Escape') { 92 | dispatch(closeForm()); 93 | } 94 | }} 95 | > 96 | My Redis : 97 | {allInstance} 98 | dispatch(showForm())}>+ 99 | {show ?
: null} 100 | { 102 | dispatch(deleteMany()); 103 | }} 104 | > 105 | Clear All 106 | 107 |
108 | ); 109 | }; 110 | 111 | export default InstanceBar; 112 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/components/StyledComponents/variables.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/components/StyledComponents/variables.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / client/components/StyledComponents variables.js

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 6/6 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 6/6 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8  74 | 1x 75 | 1x 76 | 1x 77 | 1x 78 | 1x 79 | 1x 80 |  
// text
 81 | export const primaryBlue = '#3861ed';
 82 | export const secondaryBlue = '#ebeffd';
 83 | export const primaryGrey = '#6f78ad';
 84 | export const secondaryGrey = '#9ba4d1';
 85 | export const primaryRed = '#dc143c';
 86 | export const secondaryRed = '#ff0000';
 87 |  
88 | 89 |
90 |
91 | 96 | 97 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /client/redux/globalSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import axios from 'axios'; 3 | 4 | /* - global slice stores all states that will be used everywhere in the app, 5 | such as loading, theme, clients, selectClient 6 | - it controlls the display of form and message modal windows 7 | 8 | */ 9 | 10 | const initialState = { 11 | clients: [], 12 | selectClient: '', 13 | loading: true, 14 | showForm: false, 15 | theme: 'light', 16 | message: null, 17 | }; 18 | 19 | /* - Redux thunk that posts values from form inputs to create new client 20 | - when the server is done, it displays success message, close form, 21 | and get new array of running clients 22 | */ 23 | export const addOneRedis = (form) => (dispatch, getState) => { 24 | axios 25 | .post(`http://localhost:3000/connection`, form) 26 | .then((res) => { 27 | const { setMessage, closeForm } = globalSlice.actions; 28 | dispatch(setMessage({ type: 'succeed', content: res.data })); 29 | dispatch(closeForm()); 30 | dispatch(selectClient(form.redisName)); 31 | dispatch(fetchClients()); 32 | }) 33 | .catch((err) => { 34 | if (err.response.status === 500) { 35 | dispatch(setMessage({ type: 'error', content: err.response.data })); 36 | } 37 | if (err.response.status === 400) { 38 | dispatch(setMessage({ type: 'warning', content: err.response.data })); 39 | } 40 | }); 41 | }; 42 | /* - Redux thunc that fetch all running clients from the server, it returns an 43 | array of clients names 44 | */ 45 | export const fetchClients = () => (dispatch, getState) => { 46 | axios 47 | .get(`http://localhost:3000/connection`) 48 | .then((res) => res.data) 49 | .then((data) => { 50 | dispatch(globalSlice.actions.getClients(data)); 51 | }) 52 | .catch((err) => { 53 | dispatch(setMessage({ type: 'error', content: err.response.data })); 54 | }); 55 | }; 56 | /* - deletes one client and success message */ 57 | export const deleteOne = (value) => (dispatch, getState) => { 58 | axios 59 | .delete(`http://localhost:3000/connection/deleteOne/${value}`) 60 | .then((res) => { 61 | dispatch(globalSlice.actions.deleteOne(value)); 62 | dispatch(setMessage({ type: 'succeed', content: res.data })); 63 | }) 64 | .catch((err) => { 65 | dispatch(setMessage({ type: 'error', content: err.response.data })); 66 | }); 67 | }; 68 | 69 | export const deleteMany = (value) => (dispatch, getState) => { 70 | axios 71 | .delete(`http://localhost:3000/connection/deleteMany`) 72 | .then((res) => { 73 | dispatch(globalSlice.actions.deleteMany()); 74 | dispatch(setMessage({ type: 'succeed', content: res.data })); 75 | }) 76 | .catch((err) => { 77 | dispatch(setMessage({ type: 'errors', content: err.response.data })); 78 | }); 79 | }; 80 | 81 | export const globalSlice = createSlice({ 82 | name: 'global', 83 | initialState: initialState, 84 | reducers: { 85 | startLoading: (state, action) => { 86 | state.loading = true; 87 | }, 88 | stopLoading: (state, action) => { 89 | state.loading = false; 90 | }, 91 | getClients: (state, action) => { 92 | state.clients = action.payload; 93 | }, 94 | selectClient: (state, action) => { 95 | state.selectClient = action.payload; 96 | }, 97 | showForm: (state, action) => { 98 | state.showForm = true; 99 | }, 100 | closeForm: (state, action) => { 101 | state.showForm = false; 102 | }, 103 | deleteOne: (state, action) => { 104 | state.clients = state.clients.filter( 105 | (client) => client !== action.payload 106 | ); 107 | }, 108 | themeToggle: (state, action) => { 109 | state.theme === 'light' 110 | ? (state.theme = 'dark') 111 | : (state.theme = 'light'); 112 | }, 113 | deleteMany: (state, action) => { 114 | state.clients = []; 115 | }, 116 | setMessage: (state, action) => { 117 | state.message = {}; 118 | state.message.type = action.payload.type; 119 | state.message.content = action.payload.content; 120 | }, 121 | clearMessage: (state, action) => { 122 | state.message = null; 123 | }, 124 | }, 125 | }); 126 | 127 | export const { 128 | setMessage, 129 | clearMessage, 130 | startLoading, 131 | stopLoading, 132 | getClients, 133 | selectClient, 134 | showForm, 135 | closeForm, 136 | themeToggle, 137 | } = globalSlice.actions; 138 | 139 | export default globalSlice.reducer; 140 | -------------------------------------------------------------------------------- /client/components/StyledComponents/SideBar.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { Link } from 'react-router-dom'; 3 | import { 4 | UilTachometerFast, 5 | UilDatabase, 6 | UilAnalytics, 7 | UilExclamationOctagon, 8 | UilShareAlt, 9 | UilBracketsCurly, 10 | UilSun, 11 | UilMinusCircle, 12 | } from '@iconscout/react-unicons'; 13 | import { Button } from './GlobalStyle'; 14 | import { primaryBlue } from './variables'; 15 | 16 | export const Menu = styled.div` 17 | padding: 20px 15px; 18 | position: fixed; 19 | margin-right: 50px; 20 | left: 0; 21 | width: 250px; 22 | height: 100%; 23 | display: flex; 24 | flex-direction: column; 25 | box-shadow: 0 14px 28px rgba(0, 0, 0, 0.12), 0 10px 10px rgba(0, 0, 0, 0.12); 26 | padding-bottom: 50px; 27 | .wrapper { 28 | overflow: scroll; 29 | height: 100%; 30 | margin-bottom: 40px; 31 | } 32 | `; 33 | 34 | export const SecondaryText = styled.h2` 35 | margin-left: 15px; 36 | color: ${({ theme }) => theme.textSecondary}; 37 | font-size: 1em; 38 | font-weight: 400; 39 | `; 40 | 41 | export const Logo = styled(Link)` 42 | display: flex; 43 | justify-content: flex-start; 44 | width: 100%; 45 | align-items: center; 46 | margin-bottom: 10px; 47 | margin-left: 10px; 48 | text-decoration: none; 49 | h1 { 50 | font-size: 2em; 51 | font-weight: 800; 52 | color: ${({ theme }) => theme.highlight}; 53 | } 54 | img { 55 | width: 50px; 56 | object-fit: fill; 57 | margin-right: 15px; 58 | } 59 | `; 60 | 61 | export const DeleteButton = styled(Button)` 62 | width: 35px; 63 | display: flex; 64 | align-items: center; 65 | justify-content: center; 66 | margin-left: 5px; 67 | &:hover { 68 | background-color: rgba(255, 77, 77, 0.1); 69 | } 70 | `; 71 | 72 | export const AddButton = styled(Button)` 73 | background-color: ${({ theme }) => theme.secondary}; 74 | display: flex; 75 | align-items: center; 76 | justify-content: center; 77 | width: 90%; 78 | font-size: 1.5em; 79 | margin-bottom: 5px; 80 | :hover { 81 | background-color: ${({ theme }) => theme.highlight}; 82 | color: ${({ theme }) => theme.primary}; 83 | } 84 | `; 85 | export const ClearAllButton = styled(Button)` 86 | background-color: #cc0000; 87 | width: 90%; 88 | padding: 6px 12px; 89 | color: white; 90 | font-size: 0.7em; 91 | margin-top: 5px; 92 | display: flex; 93 | justify-content: center; 94 | :hover { 95 | background-color: #ff0000; 96 | color: white; 97 | } 98 | `; 99 | 100 | export const DarkModeToggler = styled.div` 101 | position: relative; 102 | margin-left: 12px; 103 | width: 59px; 104 | left: 10px; 105 | label { 106 | position: absolute; 107 | width: 59px; 108 | height: 30px; 109 | background-color: #28292c; 110 | border-radius: 50px; 111 | cursor: pointer; 112 | input { 113 | position: absolute; 114 | display: none; 115 | } 116 | input:checked ~ .slider { 117 | background-color: #d8dbe0; 118 | } 119 | .slider::before { 120 | content: ''; 121 | position: absolute; 122 | top: 3px; 123 | left: 5px; 124 | width: 23px; 125 | height: 23px; 126 | border-radius: 50%; 127 | box-shadow: inset 8px -1px 0px 0px #d8dbe0; 128 | background-color: #28292c; 129 | transition: 0.3s; 130 | } 131 | 132 | input:checked ~ .slider::before { 133 | transform: translateX(26px); 134 | background-color: ${primaryBlue}; 135 | box-shadow: none; 136 | } 137 | 138 | .slider { 139 | position: absolute; 140 | width: 59px; 141 | height: 100%; 142 | border-radius: 50px; 143 | transition: 0.3s; 144 | } 145 | } 146 | `; 147 | 148 | export const Title = styled.h1``; 149 | 150 | export const SunIcon = styled(UilSun)` 151 | margin-right: 15px; 152 | `; 153 | 154 | export const PerfIcon = styled(UilTachometerFast)` 155 | margin-right: 15px; 156 | `; 157 | 158 | export const MemoryIcon = styled(UilDatabase)` 159 | margin-right: 15px; 160 | `; 161 | 162 | export const BasicIcon = styled(UilAnalytics)` 163 | margin-right: 15px; 164 | `; 165 | export const PersistenceIcon = styled(UilBracketsCurly)` 166 | margin-right: 15px; 167 | `; 168 | export const ErrorIcon = styled(UilExclamationOctagon)` 169 | margin-right: 15px; 170 | `; 171 | export const ClusterIcon = styled(UilShareAlt)` 172 | margin-right: 15px; 173 | `; 174 | 175 | export const DeleteIcon = styled(UilMinusCircle)` 176 | width: 45px; 177 | color: #cc0000; 178 | &:hover { 179 | color: #ff0000; 180 | } 181 | `; 182 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/components/InstanceBar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/components/InstanceBar 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files client/components/InstanceBar

23 |
24 | 25 |
26 | 5% 27 | Statements 28 | 1/20 29 |
30 | 31 | 32 |
33 | 0% 34 | Branches 35 | 0/4 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/9 43 |
44 | 45 | 46 |
47 | 6.25% 48 | Lines 49 | 1/16 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
FileStatementsBranchesFunctionsLines
InstanceBar.jsx 84 |
85 |
5%1/200%0/40%0/96.25%1/16
98 |
99 |
100 |
101 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RediSee - An Easy-To-Use Redis Visualizer for Developers 2 | 3 | 4 | RediSee is a light-weight web application that visualize Redis instances’ core metrics in a user-friendly manner. Our open-source tool is available for free and allows developers to easily monitor essential metrics such as memory usage, keyspace cache/hit ratio, evicted keys, as well as latency and throughput data. 5 |
6 |
7 | 8 | 9 | ## How Install Redis on your computer 10 | 11 | 12 |
13 | 14 | You'll will need a running Redis instance on your RAM in order to test RediSee 15 | here the steps to follow on official Redis website : 16 | 17 |
18 | 19 | 20 | ``` 21 | https://redis.io/docs/getting-started/ 22 | ``` 23 | 24 | 25 |
26 | 27 | run a local Redis database by using the command redis-server on your terminal 28 | 29 |
30 | 31 | 32 | ``` 33 | $ redis-server 34 | ``` 35 | 36 | 37 |
38 | 39 | this will start a redis instance with the default value of : port: 6379, host: 127.0.0.1 40 | You are now ready to use RediSee on your newly running Redis instance. 41 | 42 |
43 |
44 | 45 | 46 | ## How to use Redisee ? 47 | 48 | 49 |
50 | 51 | - Clone the repo to your local machine 52 | 53 | ``` 54 | git clone https://github.com/RediseeLabs/redisee.git 55 | ``` 56 | 57 |
58 | 59 | - Install all dependencies and run the webserver to run the app locally 60 | 61 | ``` 62 | npm install 63 | npm run dev 64 | ``` 65 | 66 | ​ 67 | - After the webpage has loaded, click on either the "+" or "connect" button and fill in the name of your Redis instance and fill port and host inputs (default value for local redis is port : 6379, host: 127.0.0.1). You will notice your Redis instance added on the sidebar as soon as the form has been submitted. 68 | 69 | [![Connect](https://i.gyazo.com/17b4fb62c987a268a9ace7d607eb3e73.gif)](https://gyazo.com/17b4fb62c987a268a9ace7d607eb3e73) 70 | 71 | 72 |
73 |
74 | 75 | - Click on any of the metrics buttons(Performance, Memory, etc) to monitor the health of your Redis instance. 76 | 77 | [![metrics](https://i.gyazo.com/b875f76a7c916f44c97a0f8edcbc8998.gif)](https://gyazo.com/b875f76a7c916f44c97a0f8edcbc8998) 78 | 79 | 80 |
81 |
82 | 83 | ​ 84 | - You can create multiple instances and monitor them individually. 85 | 86 | [![multiple](https://i.gyazo.com/6d826d6ff44b865d1610c5237c89e0d2.gif)](https://gyazo.com/6d826d6ff44b865d1610c5237c89e0d2) 87 | 88 | 89 |
90 |
91 | 92 | - If you no longer need to monitor them, you can clear them all at once or individually. 93 | 94 | [![clear](https://i.gyazo.com/98c2dff48b2cb3d7a9fdc0fcbbecbbe1.gif)](https://gyazo.com/98c2dff48b2cb3d7a9fdc0fcbbecbbe1) 95 | 96 | 97 |
98 |
99 | 100 | - Dark mode supported 101 | 102 | [![Image from Gyazo](https://i.gyazo.com/0914a555f0d3a69b85efb6a4badc7435.gif)](https://gyazo.com/0914a555f0d3a69b85efb6a4badc7435) 103 | 104 |
105 | 106 |
107 | 108 | ## How RediSee fetches Data 109 | 110 | ![Screenshot](dataFlow-Readme.svg) 111 | 112 | 113 | ## RediSee v1.0 features 114 | 115 |
116 | 117 | - Add, Delete, Clear redis Clients 118 | - Display all Data fetched in easy to understand graphs 119 | - Look at different category of data by using tabs 120 | - Switch theme from dark to light 121 | ​ 122 | ## Contributing 123 | ​ 124 | We welcome suggestions and pull requests! 125 | Contact us on GitHub 126 | ​ 127 |
128 | ​ 129 | ### Feature we'd love to see implemented in the future : 130 | 131 |
132 | 133 | - Abilities to records all metrics for a period of time and persist it in the database 134 | - Interpret metrics recorded. For example, a user might want to check average latency between March 18th 1pm and 6pm. 135 | - Give the user the possibility to change the interval of data being recorded (now defaults to 1s) 136 | - Refactor graphs and type of metrics fetched. Right now we're not using the most optimal graphs types for displaying data. 137 | - Interact with Redis database by SET, PUT, DELETE keys through an interface. 138 | - Being able to see data held by Redis database and Sort/filter it 139 | - Redis can run in cluster mode which means Instances are connected together, get different roles (Master/Slave) and data is sharded. Opportunities for visualization 140 | - Be able to clear datas 141 | - Sometimes redis can be protected by a password, we'll need a optionnal field in the form to handle that 142 | - Error page is under work, it need to be implemented 143 | - Implement TypeScript 144 | 145 | Testing: 146 | 147 | - Testing setup is ready but we definitely need test coverage 148 | 149 | Bugs found : 150 | 151 | - Sometime the user is not redirected correctly 152 | - Send different error status from the server to the client so Message modal can interpret response from server easily 153 | 154 | ## License 155 | 156 | [MIT](https://choosealicense.com/licenses/mit/) 157 | 158 | ## Team 159 | 160 | Garrett Yan // 161 | garrettyan6@gmail.com 162 | 163 | Anna Ivakhnik // 164 | ai1337@nyu.edu 165 | 166 | Patrice Pellan // 167 | pellan.patrice@gmail.com 168 | 169 | David Koo // 170 | jonghyunkoo92@gmail.com 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/components/StyledComponents/Themes.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/components/StyledComponents/Themes.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / client/components/StyledComponents Themes.js

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 2/2 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 2/2 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 161x 82 |   83 |   84 |   85 |   86 |   87 |   88 |   89 | 1x 90 |   91 |   92 |   93 |   94 |   95 |   96 |  
export const lightTheme = {
 97 |   primary: '#fff',
 98 |   secondary: '#ebf0fe',
 99 |   textPrimary: '#6f759e',
100 |   textSecondary: '#a7abcf',
101 |   highlight: '#5174ed',
102 | };
103 |  
104 | export const darkTheme = {
105 |   primary: '#1a1c1e',
106 |   secondary: '#2f3234',
107 |   textPrimary: '#adb0b6',
108 |   textSecondary: '#6b6e70',
109 |   highlight: '#f7f9f9',
110 | };
111 |  
112 | 113 |
114 |
115 | 120 | 121 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /server/Middleware/dataMiddleware.js: -------------------------------------------------------------------------------- 1 | const info = require('redis-info'); 2 | const fs = (module.exports = { 3 | /* - middleware that will get the client from file in redisClients folder 4 | - ask for data to the Redis database, parse it, and only return the needed properties 5 | - all middleware below does the same thing, just with different data points 6 | */ 7 | performance: (req, res, next) => { 8 | const { redisName } = req.params; 9 | const redisClient = require(`../redisClients/${redisName}.js`); 10 | let latency = 0; 11 | const start = performance.now(); 12 | redisClient 13 | .info('stats') 14 | .then((res) => { 15 | const end = performance.now(); 16 | latency = end - start; 17 | return info.parse(res); 18 | }) 19 | .then((data) => { 20 | const performance = {}; 21 | performance.latency = Number(latency); 22 | performance.iops = Number(data.instantaneous_ops_per_sec); 23 | performance.hitRate = {}; 24 | performance.hitRate.keyspace_hits = Number(data.keyspace_hits); 25 | performance.hitRate.keyspace_misses = Number(data.keyspace_misses); 26 | if (performance.hitRate.keyspace_hits) 27 | performance.hitRate.ratio = Number( 28 | (data.keyspace_hits / data.keyspace_misses + data.keyspace_hits) * 29 | 100 30 | ); 31 | 32 | res.locals.performance = performance; 33 | return next(); 34 | }) 35 | .catch((err) => { 36 | next({ 37 | log: 'error while fetching info of redis database in performance middleware', 38 | status: 500, 39 | message: { 40 | err: "can't fetch performance data on redis, make sure your redis is running", 41 | }, 42 | }); 43 | }); 44 | }, 45 | 46 | memory: (req, res, next) => { 47 | const { redisName } = req.params; 48 | const redisClient = require(`../redisClients/${redisName}.js`); 49 | redisClient 50 | .info() 51 | .then((res) => { 52 | return info.parse(res); 53 | }) 54 | .then((data) => { 55 | const memory = {}; 56 | memory.usedMemory = Number(data.used_memory); 57 | memory.memFragmentationRatio = Number(data.mem_fragmentation_ratio); 58 | memory.usedMemory = Number(data.used_memory); 59 | memory.memFragmentationRatio = Number(data.mem_fragmentation_ratio); 60 | //Evicted_keys is part of 'info stats' instead of memory 61 | memory.evictedKeys = Number(data.evicted_keys); 62 | memory.evictedKeys = Number(data.evicted_keys); 63 | res.locals.memory = memory; 64 | return next(); 65 | }) 66 | .catch((err) => { 67 | next({ 68 | log: 'error while fetching info of redis database in memory middleware', 69 | status: 500, 70 | message: { 71 | err: "can't fetch memory data on redis, make sure your redis is running", 72 | }, 73 | }); 74 | }); 75 | }, 76 | basicActivity: (req, res, next) => { 77 | const { redisName } = req.params; 78 | const redisClient = require(`../redisClients/${redisName}.js`); 79 | redisClient 80 | .info() 81 | .then((res) => { 82 | return info.parse(res); 83 | }) 84 | .then((data) => { 85 | const basicActivity = {}; 86 | basicActivity.connected_clients = Number(data.connected_clients); 87 | basicActivity.connected_slaves = Number(data.connected_slaves) 88 | ? data.connected_slaves 89 | : 0; 90 | //keyspace is part of 'info keyspace' instead of clients 91 | basicActivity.keyspace = Number( 92 | data.keyspace_hits + data.keyspace_misses 93 | ); 94 | res.locals.basicActivity = basicActivity; 95 | return next(); 96 | }) 97 | .catch((err) => { 98 | next({ 99 | log: 'error while fetching info of redis database in basicActivities middleware', 100 | status: 500, 101 | message: { 102 | err: "can't fetch basic Activities data on redis, make sure your redis is running", 103 | }, 104 | }); 105 | }); 106 | }, 107 | persistence: (req, res, next) => { 108 | const { redisName } = req.params; 109 | const redisClient = require(`../redisClients/${redisName}.js`); 110 | redisClient 111 | .info('persistence') 112 | .then((res) => { 113 | return info.parse(res); 114 | }) 115 | .then((data) => { 116 | const persistence = {}; 117 | persistence.rlst = Number(data.rdb_last_save_time); 118 | persistence.rcslt = Number(data.rdb_changes_since_last_save); 119 | res.locals.persistence = persistence; 120 | return next(); 121 | }) 122 | .catch((err) => { 123 | next({ 124 | log: 'error while fetching info of redis database in persistence middleware', 125 | status: 500, 126 | message: { 127 | err: "can't fetch persistence data on redis, make sure your redis is running", 128 | }, 129 | }); 130 | }); 131 | }, 132 | error: (req, res, next) => { 133 | const { redisName } = req.params; 134 | const redisClient = require(`../redisClients/${redisName}.js`); 135 | redisClient 136 | .info('stats') 137 | .then((res) => { 138 | return info.parse(res); 139 | }) 140 | .then((data) => { 141 | const error = {}; 142 | error.rejectedConnection = Number(data.rejected_connections); 143 | error.keyspaceMisses = Number(data.keyspace_misses); 144 | res.locals.error = error; 145 | return next(); 146 | }); 147 | }, 148 | }); 149 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/redux/store.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/redux/store.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / client/redux store.js

23 |
24 | 25 |
26 | 0% 27 | Statements 28 | 0/1 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 0% 48 | Lines 49 | 0/1 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19  85 |   86 |   87 |   88 |   89 |   90 |   91 |   92 |   93 |   94 |   95 |   96 |   97 |   98 |   99 |   100 |   101 |   102 |  
import { configureStore } from '@reduxjs/toolkit';
103 | import performance from './performanceSlice';
104 | import memory from './memorySlice';
105 | import basicActivity from './basicActivitySlice';
106 | import persistence from './persistenceSlice';
107 | import error from './errorSlice';
108 | import global from './globalSlice';
109 |  
110 | export const store = configureStore({
111 |   reducer: {
112 |     global,
113 |     performance,
114 |     memory,
115 |     basicActivity,
116 |     persistence,
117 |     error,
118 |   },
119 | });
120 |  
121 | 122 |
123 |
124 | 129 | 130 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/index.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/index.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / client index.js

23 |
24 | 25 |
26 | 0% 27 | Statements 28 | 0/3 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 0% 48 | Lines 49 | 0/3 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19  85 |   86 |   87 |   88 |   89 |   90 |   91 |   92 |   93 |   94 |   95 |   96 |   97 |   98 |   99 |   100 |   101 |   102 |  
import React from 'react';
103 | import { createRoot } from 'react-dom/client';
104 | import App from './App.jsx';
105 | import '../styles.css';
106 | import { store } from './redux/store.js';
107 | import { Provider } from 'react-redux';
108 | import { BrowserRouter } from "react-router-dom"
109 |  
110 |  
111 | const container = document.getElementById('app');
112 | const root = createRoot(container);
113 | root.render(
114 |   <BrowserRouter>
115 |     <Provider store={store}>
116 |       <App />
117 |     </Provider>
118 |   </BrowserRouter>
119 | );
120 |  
121 | 122 |
123 |
124 | 129 | 130 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/clockHelperFunction.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/clockHelperFunction.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / client clockHelperFunction.js

23 |
24 | 25 |
26 | 10% 27 | Statements 28 | 1/10 29 |
30 | 31 | 32 |
33 | 0% 34 | Branches 35 | 0/6 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/1 43 |
44 | 45 | 46 |
47 | 10% 48 | Lines 49 | 1/10 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 161x 82 |   83 |   84 |   85 |   86 |   87 |   88 |   89 |   90 |   91 |   92 |   93 |   94 |   95 |   96 |  
export const clock = (duration) => {
 97 |   // Hours, minutes and seconds
 98 |   const hrs = Math.floor(duration / 3600);
 99 |   const mins = Math.floor((duration % 3600) / 60);
100 |   const secs = Math.floor(duration % 60);
101 |  
102 |   let clock = '';
103 |  
104 |   if (hrs > 0) {
105 |     clock += '' + hrs + ':' + (mins < 10 ? '0' : '');
106 |   }
107 |   clock += '' + mins + ':' + (secs < 10 ? '0' : '');
108 |   clock += '' + secs;
109 |   return clock;
110 | };
111 |  
112 | 113 |
114 |
115 | 120 | 121 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/components/Persistence/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/components/Persistence 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files client/components/Persistence

23 |
24 | 25 |
26 | 5.26% 27 | Statements 28 | 1/19 29 |
30 | 31 | 32 |
33 | 0% 34 | Branches 35 | 0/2 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/10 43 |
44 | 45 | 46 |
47 | 7.14% 48 | Lines 49 | 1/14 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
FileStatementsBranchesFunctionsLines
PersistencePage.jsx 84 |
85 |
7.69%1/130%0/20%0/610%1/10
Rcslt.jsx 99 |
100 |
0%0/3100%0/00%0/20%0/2
Rlst.jsx 114 |
115 |
0%0/3100%0/00%0/20%0/2
128 |
129 |
130 |
131 | 136 | 137 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/components/Errors/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/components/Errors 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files client/components/Errors

23 |
24 | 25 |
26 | 4.76% 27 | Statements 28 | 1/21 29 |
30 | 31 | 32 |
33 | 0% 34 | Branches 35 | 0/2 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/10 43 |
44 | 45 | 46 |
47 | 5.88% 48 | Lines 49 | 1/17 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
FileStatementsBranchesFunctionsLines
ErrorsPage.jsx 84 |
85 |
7.69%1/130%0/20%0/610%1/10
RejectedConnections.jsx 99 |
100 |
0%0/3100%0/00%0/20%0/3
keyspaceMisses.jsx 114 |
115 |
0%0/5100%0/00%0/20%0/4
128 |
129 |
130 |
131 | 136 | 137 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /coverage/lcov-report/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | height: 100%; 4 | } 5 | body { 6 | font-family: Helvetica Neue, Helvetica, Arial; 7 | font-size: 14px; 8 | color:#333; 9 | } 10 | .small { font-size: 12px; } 11 | *, *:after, *:before { 12 | -webkit-box-sizing:border-box; 13 | -moz-box-sizing:border-box; 14 | box-sizing:border-box; 15 | } 16 | h1 { font-size: 20px; margin: 0;} 17 | h2 { font-size: 14px; } 18 | pre { 19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; 20 | margin: 0; 21 | padding: 0; 22 | -moz-tab-size: 2; 23 | -o-tab-size: 2; 24 | tab-size: 2; 25 | } 26 | a { color:#0074D9; text-decoration:none; } 27 | a:hover { text-decoration:underline; } 28 | .strong { font-weight: bold; } 29 | .space-top1 { padding: 10px 0 0 0; } 30 | .pad2y { padding: 20px 0; } 31 | .pad1y { padding: 10px 0; } 32 | .pad2x { padding: 0 20px; } 33 | .pad2 { padding: 20px; } 34 | .pad1 { padding: 10px; } 35 | .space-left2 { padding-left:55px; } 36 | .space-right2 { padding-right:20px; } 37 | .center { text-align:center; } 38 | .clearfix { display:block; } 39 | .clearfix:after { 40 | content:''; 41 | display:block; 42 | height:0; 43 | clear:both; 44 | visibility:hidden; 45 | } 46 | .fl { float: left; } 47 | @media only screen and (max-width:640px) { 48 | .col3 { width:100%; max-width:100%; } 49 | .hide-mobile { display:none!important; } 50 | } 51 | 52 | .quiet { 53 | color: #7f7f7f; 54 | color: rgba(0,0,0,0.5); 55 | } 56 | .quiet a { opacity: 0.7; } 57 | 58 | .fraction { 59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 60 | font-size: 10px; 61 | color: #555; 62 | background: #E8E8E8; 63 | padding: 4px 5px; 64 | border-radius: 3px; 65 | vertical-align: middle; 66 | } 67 | 68 | div.path a:link, div.path a:visited { color: #333; } 69 | table.coverage { 70 | border-collapse: collapse; 71 | margin: 10px 0 0 0; 72 | padding: 0; 73 | } 74 | 75 | table.coverage td { 76 | margin: 0; 77 | padding: 0; 78 | vertical-align: top; 79 | } 80 | table.coverage td.line-count { 81 | text-align: right; 82 | padding: 0 5px 0 20px; 83 | } 84 | table.coverage td.line-coverage { 85 | text-align: right; 86 | padding-right: 10px; 87 | min-width:20px; 88 | } 89 | 90 | table.coverage td span.cline-any { 91 | display: inline-block; 92 | padding: 0 5px; 93 | width: 100%; 94 | } 95 | .missing-if-branch { 96 | display: inline-block; 97 | margin-right: 5px; 98 | border-radius: 3px; 99 | position: relative; 100 | padding: 0 4px; 101 | background: #333; 102 | color: yellow; 103 | } 104 | 105 | .skip-if-branch { 106 | display: none; 107 | margin-right: 10px; 108 | position: relative; 109 | padding: 0 4px; 110 | background: #ccc; 111 | color: white; 112 | } 113 | .missing-if-branch .typ, .skip-if-branch .typ { 114 | color: inherit !important; 115 | } 116 | .coverage-summary { 117 | border-collapse: collapse; 118 | width: 100%; 119 | } 120 | .coverage-summary tr { border-bottom: 1px solid #bbb; } 121 | .keyline-all { border: 1px solid #ddd; } 122 | .coverage-summary td, .coverage-summary th { padding: 10px; } 123 | .coverage-summary tbody { border: 1px solid #bbb; } 124 | .coverage-summary td { border-right: 1px solid #bbb; } 125 | .coverage-summary td:last-child { border-right: none; } 126 | .coverage-summary th { 127 | text-align: left; 128 | font-weight: normal; 129 | white-space: nowrap; 130 | } 131 | .coverage-summary th.file { border-right: none !important; } 132 | .coverage-summary th.pct { } 133 | .coverage-summary th.pic, 134 | .coverage-summary th.abs, 135 | .coverage-summary td.pct, 136 | .coverage-summary td.abs { text-align: right; } 137 | .coverage-summary td.file { white-space: nowrap; } 138 | .coverage-summary td.pic { min-width: 120px !important; } 139 | .coverage-summary tfoot td { } 140 | 141 | .coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | .coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | .coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | .status-line { height: 10px; } 155 | /* yellow */ 156 | .cbranch-no { background: yellow !important; color: #111; } 157 | /* dark red */ 158 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } 159 | .low .chart { border:1px solid #C21F39 } 160 | .highlighted, 161 | .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ 162 | background: #C21F39 !important; 163 | } 164 | /* medium red */ 165 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } 166 | /* light red */ 167 | .low, .cline-no { background:#FCE1E5 } 168 | /* light green */ 169 | .high, .cline-yes { background:rgb(230,245,208) } 170 | /* medium green */ 171 | .cstat-yes { background:rgb(161,215,106) } 172 | /* dark green */ 173 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) } 174 | .high .chart { border:1px solid rgb(77,146,33) } 175 | /* dark yellow (gold) */ 176 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; } 177 | .medium .chart { border:1px solid #f9cd0b; } 178 | /* light yellow */ 179 | .medium { background: #fff4c2; } 180 | 181 | .cstat-skip { background: #ddd; color: #111; } 182 | .fstat-skip { background: #ddd; color: #111 !important; } 183 | .cbranch-skip { background: #ddd !important; color: #111; } 184 | 185 | span.cline-neutral { background: #eaeaea; } 186 | 187 | .coverage-summary td.empty { 188 | opacity: .5; 189 | padding-top: 4px; 190 | padding-bottom: 4px; 191 | line-height: 1; 192 | color: #888; 193 | } 194 | 195 | .cover-fill, .cover-empty { 196 | display:inline-block; 197 | height: 12px; 198 | } 199 | .chart { 200 | line-height: 0; 201 | } 202 | .cover-empty { 203 | background: white; 204 | } 205 | .cover-full { 206 | border-right: none !important; 207 | } 208 | pre.prettyprint { 209 | border: none !important; 210 | padding: 0 !important; 211 | margin: 0 !important; 212 | } 213 | .com { color: #999 !important; } 214 | .ignore-none { color: #999; font-weight: normal; } 215 | 216 | .wrapper { 217 | min-height: 100%; 218 | height: auto !important; 219 | height: 100%; 220 | margin: 0 auto -48px; 221 | } 222 | .footer, .push { 223 | height: 48px; 224 | } 225 | -------------------------------------------------------------------------------- /server/Middleware/connectionMiddleware.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const redis = require('redis'); 4 | 5 | /* - helper function that returns the contents 6 | for newly created file will be used to create client 7 | - */ 8 | 9 | const createFileContent = (host, port) => { 10 | return `var redis = require("redis"), 11 | client = redis.createClient('${port}', '${host}'); 12 | client.connect() 13 | module.exports = client;`; 14 | }; 15 | 16 | module.exports = { 17 | /* - middleware that will check values from the form and send back an error message if it doesn't meet condition */ 18 | validate: (req, res, next) => { 19 | const { host, port, redisName } = req.body; 20 | /* - regEx that spots special character */ 21 | const excludeRegex = /^[<>!@#\$%\^\&*\)\(+=._-]+$/g; 22 | /* - regEx that selects numbers */ 23 | const numberRegex = /[0-9]/g; 24 | 25 | let error = { 26 | log: 'Error triggered in validate middleware', 27 | status: 400, 28 | message: { 29 | err: '', 30 | }, 31 | }; 32 | /* - checking if field is empty */ 33 | for (let key in req.body) { 34 | if (req.body[key].length <= 0) { 35 | return next({ 36 | ...error, 37 | message: { err: `${key} must not be empty` }, 38 | }); 39 | } 40 | } 41 | if (typeof redisName !== 'string') 42 | return next({ ...error, message: { err: 'Name must not be a Number' } }); 43 | /* - check if Redis name input contain any special character */ 44 | if (excludeRegex.test(redisName)) { 45 | return next({ 46 | ...error, 47 | message: { err: 'Name must no contain any special character' }, 48 | }); 49 | } 50 | /* - check if input port contains any character other than number */ 51 | if (!numberRegex.test(port)) 52 | return next({ 53 | ...error, 54 | message: { err: 'Port must contain only Numbers' }, 55 | }); 56 | if (redisName.length >= 15) 57 | return next({ 58 | ...error, 59 | message: { err: 'name must be less than 15 characters' }, 60 | }); 61 | return next(); 62 | }, 63 | /* - this middleware will take info from the form, create client, and connect it to Redis database */ 64 | connect: async (req, res, next) => { 65 | const { redisName, port, host } = req.body; 66 | try { 67 | const Client = await redis.createClient({ 68 | socket: { 69 | host: host, 70 | port: port, 71 | }, 72 | }); 73 | /* - try to connect to database with form info */ 74 | await Client.connect(); 75 | 76 | /* - if successfully connected, create a new JS file inside redisClients folder, 77 | that will contain the new client, thanks to the createFileContents helper function 78 | */ 79 | fs.writeFileSync( 80 | path.resolve(__dirname, `../redisClients/${redisName}.js`), 81 | createFileContent(host, port), 82 | function (err) { 83 | throw 'error while creating client'; 84 | } 85 | ); 86 | 87 | res.locals.redisName = redisName; 88 | next(); 89 | } catch (err) { 90 | next({ 91 | log: 'Error when validating redis instance in connection Middleware', 92 | status: 500, 93 | message: { 94 | err: 'error while connection to redis, please check port and host', 95 | }, 96 | }); 97 | } 98 | }, 99 | /* - middleware that gets all files stored in redisClients folder, and returns an array of client names */ 100 | getInstances: (req, res, next) => { 101 | /* - if redisClient is deleted by github because it's empty, this will create 102 | folder is named "redisClient" 103 | */ 104 | const redisClientFolder = path.resolve(__dirname, `../redisClients`); 105 | if (!fs.existsSync(redisClientFolder)) { 106 | fs.mkdirSync(redisClientFolder); 107 | } 108 | 109 | fs.readdir( 110 | path.resolve(__dirname, '../redisClients'), 111 | { withFileTypes: false }, 112 | (err, files) => { 113 | if (err) { 114 | next({ 115 | log: 'Error when reading all redis instances in getInstances Middleware', 116 | status: 500, 117 | message: { 118 | err: "couldn't fetch Clients please retry", 119 | }, 120 | }); 121 | } else { 122 | files = files.map((file) => file.slice(0, -3)); 123 | res.locals.instancesArr = files; 124 | next(); 125 | } 126 | } 127 | ); 128 | }, 129 | /* - middleware that will delete file (client) with info given from front-end */ 130 | disconnectOne: async (req, res, next) => { 131 | const { redisName } = req.params; 132 | console.log(redisName); 133 | try { 134 | const redisClient = require(`../redisClients/${redisName}.js`); 135 | await fs.promises.unlink( 136 | path.resolve(__dirname, `../redisClients/${redisName}.js`) 137 | ); 138 | await redisClient.disconnect(); 139 | } catch (err) { 140 | return next({ 141 | log: 'error while deleting file in connectionMiddleware', 142 | status: 500, 143 | message: { 144 | err: 'could not find redis client to delete, please retry', 145 | }, 146 | }); 147 | } 148 | next(); 149 | }, 150 | /* - middleware that will delete all files */ 151 | disconnectMany: (req, res, next) => { 152 | fs.readdir(path.resolve(__dirname, '../redisClients'), (err, files) => { 153 | if (err) { 154 | next({ 155 | log: 'error while deleting all files in disconnectMany Middleware', 156 | status: 500, 157 | message: { 158 | err: 'could not find redis clients to delete, please retry', 159 | }, 160 | }); 161 | } 162 | for (let file of files) { 163 | fs.unlink( 164 | path.resolve(__dirname, `../redisClients/${file}`), 165 | function (err) { 166 | if (err) { 167 | next({ 168 | log: 'error while deleting file in disconnectMany Middleware', 169 | status: 500, 170 | message: { 171 | err: 'could not find redis clients to delete, please retry', 172 | }, 173 | }); 174 | } 175 | } 176 | ); 177 | } 178 | }); 179 | 180 | next(); 181 | }, 182 | }; 183 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/redux/submitFormSlice.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/redux/submitFormSlice.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / client/redux submitFormSlice.js

23 |
24 | 25 |
26 | 0% 27 | Statements 28 | 0/6 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/3 43 |
44 | 45 | 46 |
47 | 0% 48 | Lines 49 | 0/6 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19 85 | 20 86 | 21  87 |   88 |   89 |   90 |   91 |   92 |   93 |   94 |   95 |   96 |   97 |   98 |   99 |   100 |   101 |   102 |   103 |   104 |   105 |   106 |  
import { createSlice } from '@reduxjs/toolkit';
107 | import axios from 'axios';
108 |  
109 | const initialState = {
110 |   totalInstances: [],
111 | };
112 | export const submitForm = (e) => {
113 |   e.preventDefault();
114 |  
115 |   //values need to be dynamic, from input box
116 |   axios
117 |     .post('http://localhost:3000/newInstance', {
118 |       username: 'garrett',
119 |       password: 123,
120 |       port: 1,
121 |       host: '127.0.0.1:6379',
122 |     })
123 |     .then((res) => console.log(res))
124 |     .catch((err) => console.log(err));
125 | };
126 |  
127 | 128 |
129 |
130 | 135 | 136 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/components/Toggler.jsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/components/Toggler.jsx 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / client/components Toggler.jsx

23 |
24 | 25 |
26 | 16.66% 27 | Statements 28 | 1/6 29 |
30 | 31 | 32 |
33 | 0% 34 | Branches 35 | 0/2 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/3 43 |
44 | 45 | 46 |
47 | 20% 48 | Lines 49 | 1/5 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19 85 | 20 86 | 21 87 | 22 88 | 23 89 | 24  90 |   91 |   92 |   93 |   94 | 1x 95 |   96 |   97 |   98 |   99 |   100 |   101 |   102 |   103 |   104 |   105 |   106 |   107 |   108 |   109 |   110 |   111 |   112 |  
import React from 'react';
113 | import { DarkModeToggler } from './StyledComponents/SideBar.js';
114 | import { themeToggle } from '../redux/globalSlice';
115 | import { useDispatch, useSelector } from 'react-redux';
116 |  
117 | const Toggler = (props) => {
118 |   const dispatch = useDispatch();
119 |   const theme = useSelector((state) => state.global.theme);
120 |   return (
121 |     <DarkModeToggler>
122 |       <label>
123 |         <input
124 |           onChange={() => dispatch(themeToggle())}
125 |           type="checkbox"
126 |           checked={theme === 'light' ? true : false}
127 |         />
128 |         <span className="slider"></span>
129 |       </label>
130 |     </DarkModeToggler>
131 |   );
132 | };
133 |  
134 | export default Toggler;
135 |  
136 | 137 |
138 |
139 | 144 | 145 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var addSorting = (function() { 3 | 'use strict'; 4 | var cols, 5 | currentSort = { 6 | index: 0, 7 | desc: false 8 | }; 9 | 10 | // returns the summary table element 11 | function getTable() { 12 | return document.querySelector('.coverage-summary'); 13 | } 14 | // returns the thead element of the summary table 15 | function getTableHeader() { 16 | return getTable().querySelector('thead tr'); 17 | } 18 | // returns the tbody element of the summary table 19 | function getTableBody() { 20 | return getTable().querySelector('tbody'); 21 | } 22 | // returns the th element for nth column 23 | function getNthColumn(n) { 24 | return getTableHeader().querySelectorAll('th')[n]; 25 | } 26 | 27 | function onFilterInput() { 28 | const searchValue = document.getElementById('fileSearch').value; 29 | const rows = document.getElementsByTagName('tbody')[0].children; 30 | for (let i = 0; i < rows.length; i++) { 31 | const row = rows[i]; 32 | if ( 33 | row.textContent 34 | .toLowerCase() 35 | .includes(searchValue.toLowerCase()) 36 | ) { 37 | row.style.display = ''; 38 | } else { 39 | row.style.display = 'none'; 40 | } 41 | } 42 | } 43 | 44 | // loads the search box 45 | function addSearchBox() { 46 | var template = document.getElementById('filterTemplate'); 47 | var templateClone = template.content.cloneNode(true); 48 | templateClone.getElementById('fileSearch').oninput = onFilterInput; 49 | template.parentElement.appendChild(templateClone); 50 | } 51 | 52 | // loads all columns 53 | function loadColumns() { 54 | var colNodes = getTableHeader().querySelectorAll('th'), 55 | colNode, 56 | cols = [], 57 | col, 58 | i; 59 | 60 | for (i = 0; i < colNodes.length; i += 1) { 61 | colNode = colNodes[i]; 62 | col = { 63 | key: colNode.getAttribute('data-col'), 64 | sortable: !colNode.getAttribute('data-nosort'), 65 | type: colNode.getAttribute('data-type') || 'string' 66 | }; 67 | cols.push(col); 68 | if (col.sortable) { 69 | col.defaultDescSort = col.type === 'number'; 70 | colNode.innerHTML = 71 | colNode.innerHTML + ''; 72 | } 73 | } 74 | return cols; 75 | } 76 | // attaches a data attribute to every tr element with an object 77 | // of data values keyed by column name 78 | function loadRowData(tableRow) { 79 | var tableCols = tableRow.querySelectorAll('td'), 80 | colNode, 81 | col, 82 | data = {}, 83 | i, 84 | val; 85 | for (i = 0; i < tableCols.length; i += 1) { 86 | colNode = tableCols[i]; 87 | col = cols[i]; 88 | val = colNode.getAttribute('data-value'); 89 | if (col.type === 'number') { 90 | val = Number(val); 91 | } 92 | data[col.key] = val; 93 | } 94 | return data; 95 | } 96 | // loads all row data 97 | function loadData() { 98 | var rows = getTableBody().querySelectorAll('tr'), 99 | i; 100 | 101 | for (i = 0; i < rows.length; i += 1) { 102 | rows[i].data = loadRowData(rows[i]); 103 | } 104 | } 105 | // sorts the table using the data for the ith column 106 | function sortByIndex(index, desc) { 107 | var key = cols[index].key, 108 | sorter = function(a, b) { 109 | a = a.data[key]; 110 | b = b.data[key]; 111 | return a < b ? -1 : a > b ? 1 : 0; 112 | }, 113 | finalSorter = sorter, 114 | tableBody = document.querySelector('.coverage-summary tbody'), 115 | rowNodes = tableBody.querySelectorAll('tr'), 116 | rows = [], 117 | i; 118 | 119 | if (desc) { 120 | finalSorter = function(a, b) { 121 | return -1 * sorter(a, b); 122 | }; 123 | } 124 | 125 | for (i = 0; i < rowNodes.length; i += 1) { 126 | rows.push(rowNodes[i]); 127 | tableBody.removeChild(rowNodes[i]); 128 | } 129 | 130 | rows.sort(finalSorter); 131 | 132 | for (i = 0; i < rows.length; i += 1) { 133 | tableBody.appendChild(rows[i]); 134 | } 135 | } 136 | // removes sort indicators for current column being sorted 137 | function removeSortIndicators() { 138 | var col = getNthColumn(currentSort.index), 139 | cls = col.className; 140 | 141 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 142 | col.className = cls; 143 | } 144 | // adds sort indicators for current column being sorted 145 | function addSortIndicators() { 146 | getNthColumn(currentSort.index).className += currentSort.desc 147 | ? ' sorted-desc' 148 | : ' sorted'; 149 | } 150 | // adds event listeners for all sorter widgets 151 | function enableUI() { 152 | var i, 153 | el, 154 | ithSorter = function ithSorter(i) { 155 | var col = cols[i]; 156 | 157 | return function() { 158 | var desc = col.defaultDescSort; 159 | 160 | if (currentSort.index === i) { 161 | desc = !currentSort.desc; 162 | } 163 | sortByIndex(i, desc); 164 | removeSortIndicators(); 165 | currentSort.index = i; 166 | currentSort.desc = desc; 167 | addSortIndicators(); 168 | }; 169 | }; 170 | for (i = 0; i < cols.length; i += 1) { 171 | if (cols[i].sortable) { 172 | // add the click event handler on the th so users 173 | // dont have to click on those tiny arrows 174 | el = getNthColumn(i).querySelector('.sorter').parentElement; 175 | if (el.addEventListener) { 176 | el.addEventListener('click', ithSorter(i)); 177 | } else { 178 | el.attachEvent('onclick', ithSorter(i)); 179 | } 180 | } 181 | } 182 | } 183 | // adds sorting functionality to the UI 184 | return function() { 185 | if (!getTable()) { 186 | return; 187 | } 188 | cols = loadColumns(); 189 | loadData(); 190 | addSearchBox(); 191 | addSortIndicators(); 192 | enableUI(); 193 | }; 194 | })(); 195 | 196 | window.addEventListener('load', addSorting); 197 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/components/Performance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/components/Performance 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files client/components/Performance

23 |
24 | 25 |
26 | 3.84% 27 | Statements 28 | 1/26 29 |
30 | 31 | 32 |
33 | 0% 34 | Branches 35 | 0/2 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/13 43 |
44 | 45 | 46 |
47 | 5.26% 48 | Lines 49 | 1/19 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 |
FileStatementsBranchesFunctionsLines
HitRate.jsx 84 |
85 |
0%0/6100%0/00%0/30%0/4
Iops.jsx 99 |
100 |
0%0/3100%0/00%0/20%0/2
Latency.jsx 114 |
115 |
0%0/4100%0/00%0/20%0/3
PerformancePage.jsx 129 |
130 |
7.69%1/130%0/20%0/610%1/10
143 |
144 |
145 |
146 | 151 | 152 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/components/BasicActivities/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/components/BasicActivities 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files client/components/BasicActivities

23 |
24 | 25 |
26 | 4.34% 27 | Statements 28 | 1/23 29 |
30 | 31 | 32 |
33 | 0% 34 | Branches 35 | 0/2 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/12 43 |
44 | 45 | 46 |
47 | 5.26% 48 | Lines 49 | 1/19 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 |
FileStatementsBranchesFunctionsLines
ActivitiesPage.jsx 84 |
85 |
7.69%1/130%0/20%0/610%1/10
ConnectedClient.jsx 99 |
100 |
0%0/3100%0/00%0/20%0/3
ConnectedSlaves.jsx 114 |
115 |
0%0/3100%0/00%0/20%0/3
Keyspace.jsx 129 |
130 |
0%0/4100%0/00%0/20%0/3
143 |
144 |
145 |
146 | 151 | 152 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/components/Memory/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/components/Memory 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files client/components/Memory

23 |
24 | 25 |
26 | 13.79% 27 | Statements 28 | 4/29 29 |
30 | 31 | 32 |
33 | 0% 34 | Branches 35 | 0/2 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/13 43 |
44 | 45 | 46 |
47 | 17.39% 48 | Lines 49 | 4/23 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 |
FileStatementsBranchesFunctionsLines
EvictedKeys.jsx 84 |
85 |
25%1/4100%0/00%0/233.33%1/3
FragRatioGraph.jsx 99 |
100 |
25%1/4100%0/00%0/225%1/4
MemoryPage.jsx 114 |
115 |
5.88%1/170%0/20%0/77.69%1/13
UsedMemoryGraph.jsx 129 |
130 |
25%1/4100%0/00%0/233.33%1/3
143 |
144 |
145 |
146 | 151 | 152 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /coverage/lcov-report/client/components/HomePage.jsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for client/components/HomePage.jsx 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / client/components HomePage.jsx

23 |
24 | 25 |
26 | 25% 27 | Statements 28 | 1/4 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/2 43 |
44 | 45 | 46 |
47 | 25% 48 | Lines 49 | 1/4 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19 85 | 20 86 | 21 87 | 22 88 | 23 89 | 24 90 | 25 91 | 26  92 |   93 |   94 |   95 |   96 | 1x 97 |   98 |   99 |   100 |   101 |   102 |   103 |   104 |   105 |   106 |   107 |   108 |   109 |   110 |   111 |   112 |   113 |   114 |   115 |   116 |  
import React from 'react';
117 | import { Page, HomeButton } from './StyledComponents/HomePage';
118 | import url from './StyledComponents/homeImage.svg';
119 | import { useDispatch } from 'react-redux';
120 | import { showForm } from '../redux/globalSlice';
121 | const HomePage = (prop) => {
122 |   const dispatch = useDispatch();
123 |   return (
124 |     <Page>
125 |       <div className="container">
126 |         <h1 className="title">Welcome to RediSee</h1>
127 |         <h3 className="subtitle">
128 |           Get live data from your redis databe with this lightweight monitoring
129 |           tool
130 |         </h3>
131 |         <HomeButton onClick={() => dispatch(showForm())}>
132 |           Connect now
133 |         </HomeButton>
134 |       </div>
135 |       <img src={url} alt="Loading" style={{ height: '95%' }} />
136 |     </Page>
137 |   );
138 | };
139 |  
140 | export default HomePage;
141 |  
142 | 143 |
144 |
145 | 150 | 151 | 156 | 157 | 158 | 159 | 160 | --------------------------------------------------------------------------------