├── .DS_Store
├── .eslintrc.json
├── .gitignore
├── LICENSE
├── README.md
├── __mocks__
└── styleMock.js
├── __tests__
├── enzyme.test.js
└── server.test.js
├── babel.config.js
├── dist
├── main.js
└── main.js.map
├── electron
└── main.ts
├── index.js
├── package.json
├── pull_request_template.md
├── server
├── Controllers
│ ├── loginController.js
│ └── redisController.js
├── LoginModel.js
└── server.js
├── src
├── Connect.jsx
├── Home.jsx
├── Login.jsx
├── Metrics
│ ├── Latency.jsx
│ ├── Memory.jsx
│ └── Throughput.jsx
├── Navigation.jsx
├── SignUp.jsx
├── app.jsx
├── context
│ ├── ContextProvider.js
│ └── index.ts
├── public
│ └── index.html
└── styles
│ ├── assets
│ ├── connection.gif
│ ├── git.png
│ ├── latency.gif
│ ├── linkedin.png
│ ├── login.gif
│ ├── logout.gif
│ ├── memory.gif
│ ├── redisrate1.png
│ ├── signup.gif
│ └── throughput.gif
│ └── styles.css
├── tsconfig.json
├── webpack.electron.config.js
└── webpack.react.config.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/RedisRate/aff10f41762557aec13b06c84203f201a1da1f4c/.DS_Store
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true
5 | },
6 | "extends": "plugin:react/recommended",
7 | "parserOptions": {
8 | "ecmaFeatures": {
9 | "jsx": true
10 | },
11 | "ecmaVersion": 12,
12 | "sourceType": "module"
13 | },
14 | "plugins": [
15 | "react"
16 | ],
17 | "rules": {
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .env
3 | .DS_Store
4 | dump.rdb
5 | package-lock.json
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 OSLabs Beta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | RedisRate is a simple to use and use-case agnostic analytic tool built to aid developers in making informed database management descisions through visualization of key Redis performance metrics.
6 |
7 | ## Beta Phase
8 |
9 | RedisRate is in BETA phase. Pull requests and contributions are welcome. Create an issue or contact a member of the team if you would like to contribute.
10 |
11 | ## Getting Started 🏁
12 |
13 | 1. Clone this repository.
14 | 2. Run `npm install`
15 | 3. To run application,
16 | `npm run redis-rate`
17 |
18 | ## ✨ Usage
19 |
20 |
21 | Sign in by entering your username and password.
22 |
23 |
24 |
25 |
26 |
27 | If this is your first time using the application, create an account by clicking sign up.
28 |
29 |
30 |
31 |
32 |
33 |
34 | Once logged in, make sure your Redis server is running and connect your Redis database by entering its IP address, port number, and Redis auth password. If you have not configured your Redis Server to require a password, leave the password input field blank as the app can connect without it. Upon successful connection, use the navigation menu to view performance metrics of your Redis instance.
35 |
36 |
37 |
38 | Memory
39 |
40 |
41 |
42 |
43 |
44 | Latency
45 |
46 |
47 |
48 |
49 |
50 | Throughput
51 |
52 |
53 |
54 |
55 |
56 | ## Developers
57 |
58 |
59 |
60 | 
61 |
62 | [Matthew Marchand](https://github.com/m-marchand)
63 |
64 | [Navi Dhillon](https://github.com/Super-Programmer-Navi)
65 |
66 | [Heidi Bang](https://github.com/heidibang)
67 |
68 | [Dawn Chung](https://github.com/dawn-chung27)
69 |
70 | ## 📝 License
71 |
72 | [MIT](https://choosealicense.com/licenses/mit/)
73 |
--------------------------------------------------------------------------------
/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
--------------------------------------------------------------------------------
/__tests__/enzyme.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { configure, shallow } from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 |
5 | import Connect from '../src/Connect.jsx';
6 | import Navigation from '../src/Navigation.jsx';
7 | import Home from '../src/Home.jsx';
8 |
9 |
10 | configure({ adapter: new Adapter() });
11 |
12 | const wrapper = shallow()
13 |
14 | describe('Connection Page', () => {
15 |
16 | it("Test", () => {
17 | expect(wrapper.contains([Hello, World
])).toBe(false);
18 | })
19 |
20 | it("Renders a form", () => {
21 | expect(wrapper.find("form"))
22 | })
23 |
24 | it("Renders elements with class name formElement", () => {
25 | expect(wrapper.find("form").hasClass("formElement"))
26 | })
27 |
28 | it("Renders a submit button", () => {
29 | expect(wrapper.find("submit"))
30 | })
31 |
32 | });
33 |
34 | describe('Logged in user pages', () => {
35 |
36 | describe('Navigation', () => {
37 |
38 | const wrapper = shallow()
39 |
40 | it('should have navigation menu', () => {
41 | expect(wrapper.find('div'));
42 | expect(wrapper.find('main'));
43 | })
44 | })
45 |
46 | describe('Home', () => {
47 | const wrapper = shallow()
48 | const port = '';
49 | const ipaddress = '';
50 |
51 | it('should have a port and ip address', () => {
52 | expect(wrapper.render(port))
53 | expect(wrapper.render(ipaddress))
54 | })
55 | })
56 |
57 | })
58 |
--------------------------------------------------------------------------------
/__tests__/server.test.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const request = require('supertest');
3 | const server = 'http://localhost:4000';
4 | require('dotenv').config()
5 |
6 |
7 | describe('User route', () => {
8 |
9 | const MONGO_URI = process.env.SECRET;
10 |
11 | beforeAll((done) => {
12 | mongoose.connect(MONGO_URI);
13 | done();
14 | })
15 |
16 | describe('/login', () => {
17 | it('checks username and password and signs in user', () => {
18 | return request(server)
19 | .post('/login')
20 | .send({
21 | username: 'mango',
22 | password: 'mango'
23 | })
24 | .expect('Content-Type', /json/)
25 | .expect({isUserLoggedIn: true})
26 | .expect(200);
27 | });
28 | });
29 |
30 | describe('/signup', () => {
31 | it('adds user to database', () => {
32 | return request(server)
33 | .post('/signup')
34 | .send({
35 | firstName: 'First',
36 | lastName: 'Last',
37 | username: 'test',
38 | password: 'test'
39 | })
40 | .expect('Content-Type', /json/)
41 | .expect({isUserLoggedIn: true})
42 | .expect(200);
43 | });
44 | });
45 |
46 | describe('/connect', () => {
47 | it('connects to database', () => {
48 | return request(server)
49 | .post('/connect')
50 | .send({
51 | port: '6379',
52 | ipaddress: '127.0.0.1',
53 | password: 'foobared'
54 | })
55 | .expect('Content-Type', /json/)
56 | .expect(200);
57 | });
58 | });
59 |
60 | afterAll((done) => {
61 | mongoose.connection.close(MONGO_URI);
62 | done();
63 | })
64 | });
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@babel/preset-env',
4 | '@babel/preset-react',
5 | '@babel/preset-typescript'
6 | ]
7 | }
--------------------------------------------------------------------------------
/dist/main.js:
--------------------------------------------------------------------------------
1 | /******/ (() => { // webpackBootstrap
2 | /******/ "use strict";
3 | /******/ var __webpack_modules__ = ({
4 |
5 | /***/ "./electron/main.ts":
6 | /*!**************************!*\
7 | !*** ./electron/main.ts ***!
8 | \**************************/
9 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
10 |
11 | __webpack_require__.r(__webpack_exports__);
12 | /* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! electron */ "electron");
13 | /* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(electron__WEBPACK_IMPORTED_MODULE_0__);
14 | /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! path */ "path");
15 | /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__);
16 |
17 |
18 | var mainWindow;
19 |
20 | function createWindow() {
21 | mainWindow = new electron__WEBPACK_IMPORTED_MODULE_0__.BrowserWindow({
22 | width: 800,
23 | height: 700,
24 | // backgroundColor: "#45aaf2",
25 | webPreferences: {
26 | nodeIntegration: true
27 | }
28 | });
29 |
30 | if (true) {
31 | mainWindow.loadURL("http://localhost:3000");
32 | } else {}
33 |
34 | mainWindow.on('closed', function () {
35 | mainWindow = null;
36 | });
37 | }
38 |
39 | electron__WEBPACK_IMPORTED_MODULE_0__.app.on('ready', createWindow);
40 | electron__WEBPACK_IMPORTED_MODULE_0__.app.allowRendererProcessReuse = true;
41 |
42 | /***/ }),
43 |
44 | /***/ "electron":
45 | /*!***************************!*\
46 | !*** external "electron" ***!
47 | \***************************/
48 | /***/ ((module) => {
49 |
50 | module.exports = require("electron");;
51 |
52 | /***/ }),
53 |
54 | /***/ "path":
55 | /*!***********************!*\
56 | !*** external "path" ***!
57 | \***********************/
58 | /***/ ((module) => {
59 |
60 | module.exports = require("path");;
61 |
62 | /***/ })
63 |
64 | /******/ });
65 | /************************************************************************/
66 | /******/ // The module cache
67 | /******/ var __webpack_module_cache__ = {};
68 | /******/
69 | /******/ // The require function
70 | /******/ function __webpack_require__(moduleId) {
71 | /******/ // Check if module is in cache
72 | /******/ if(__webpack_module_cache__[moduleId]) {
73 | /******/ return __webpack_module_cache__[moduleId].exports;
74 | /******/ }
75 | /******/ // Create a new module (and put it into the cache)
76 | /******/ var module = __webpack_module_cache__[moduleId] = {
77 | /******/ // no module.id needed
78 | /******/ // no module.loaded needed
79 | /******/ exports: {}
80 | /******/ };
81 | /******/
82 | /******/ // Execute the module function
83 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
84 | /******/
85 | /******/ // Return the exports of the module
86 | /******/ return module.exports;
87 | /******/ }
88 | /******/
89 | /************************************************************************/
90 | /******/ /* webpack/runtime/compat get default export */
91 | /******/ (() => {
92 | /******/ // getDefaultExport function for compatibility with non-harmony modules
93 | /******/ __webpack_require__.n = (module) => {
94 | /******/ var getter = module && module.__esModule ?
95 | /******/ () => (module['default']) :
96 | /******/ () => (module);
97 | /******/ __webpack_require__.d(getter, { a: getter });
98 | /******/ return getter;
99 | /******/ };
100 | /******/ })();
101 | /******/
102 | /******/ /* webpack/runtime/define property getters */
103 | /******/ (() => {
104 | /******/ // define getter functions for harmony exports
105 | /******/ __webpack_require__.d = (exports, definition) => {
106 | /******/ for(var key in definition) {
107 | /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
108 | /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
109 | /******/ }
110 | /******/ }
111 | /******/ };
112 | /******/ })();
113 | /******/
114 | /******/ /* webpack/runtime/hasOwnProperty shorthand */
115 | /******/ (() => {
116 | /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
117 | /******/ })();
118 | /******/
119 | /******/ /* webpack/runtime/make namespace object */
120 | /******/ (() => {
121 | /******/ // define __esModule on exports
122 | /******/ __webpack_require__.r = (exports) => {
123 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
124 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
125 | /******/ }
126 | /******/ Object.defineProperty(exports, '__esModule', { value: true });
127 | /******/ };
128 | /******/ })();
129 | /******/
130 | /************************************************************************/
131 | /******/ // startup
132 | /******/ // Load entry module
133 | /******/ __webpack_require__("./electron/main.ts");
134 | /******/ // This entry module used 'exports' so it can't be inlined
135 | /******/ })()
136 | ;
137 | //# sourceMappingURL=main.js.map
--------------------------------------------------------------------------------
/dist/main.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack://RedisRate/./electron/main.ts","webpack://RedisRate/external \"electron\"","webpack://RedisRate/external \"path\"","webpack://RedisRate/webpack/bootstrap","webpack://RedisRate/webpack/runtime/compat get default export","webpack://RedisRate/webpack/runtime/define property getters","webpack://RedisRate/webpack/runtime/hasOwnProperty shorthand","webpack://RedisRate/webpack/runtime/make namespace object","webpack://RedisRate/webpack/startup"],"names":["mainWindow","createWindow","BrowserWindow","width","height","webPreferences","nodeIntegration","process","loadURL","on","app"],"mappings":";;;;;;;;;;;;;;;AAAA;AACA;AAEA,IAAIA,UAAJ;;AAEA,SAASC,YAAT,GAAwB;AACtBD,YAAU,GAAG,IAAIE,mDAAJ,CAAkB;AAC7BC,SAAK,EAAE,GADsB;AAE7BC,UAAM,EAAE,GAFqB;AAG7B;AACAC,kBAAc,EAAE;AACdC,qBAAe,EAAE;AADH;AAJa,GAAlB,CAAb;;AASA,MAAIC,IAAJ,EAA4C;AAC1CP,cAAU,CAACQ,OAAX;AACD,GAFD,MAEO,EAEN;;AAEDR,YAAU,CAACS,EAAX,CAAc,QAAd,EAAwB,YAAM;AAC5BT,cAAU,GAAG,IAAb;AACD,GAFD;AAGD;;AAEDU,4CAAA,CAAO,OAAP,EAAgBT,YAAhB;AACAS,mEAAA,GAAgC,IAAhC,C;;;;;;;;;;AC3BA,sC;;;;;;;;;;ACAA,kC;;;;;;UCAA;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;;;;WCrBA;WACA;WACA;WACA;WACA;WACA,gCAAgC,YAAY;WAC5C;WACA,E;;;;;WCPA;WACA;WACA;WACA;WACA,wCAAwC,yCAAyC;WACjF;WACA;WACA,E;;;;;WCPA,wF;;;;;WCAA;WACA;WACA;WACA,sDAAsD,kBAAkB;WACxE;WACA,+CAA+C,cAAc;WAC7D,E;;;;UCNA;UACA;UACA;UACA","file":"main.js","sourcesContent":["import { app, BrowserWindow } from 'electron';\nimport * as path from 'path';\n\nlet mainWindow: Electron.BrowserWindow | null;\n\nfunction createWindow() {\n mainWindow = new BrowserWindow({\n width: 800,\n height: 700,\n // backgroundColor: \"#45aaf2\",\n webPreferences: {\n nodeIntegration: true,\n },\n });\n\n if (process.env.NODE_ENV === 'development') {\n mainWindow.loadURL(`http://localhost:3000`);\n } else {\n mainWindow.loadURL(path.resolve(__dirname, '../index.html'));\n }\n\n mainWindow.on('closed', () => {\n mainWindow = null;\n });\n}\n\napp.on('ready', createWindow);\napp.allowRendererProcessReuse = true;\n","module.exports = require(\"electron\");;","module.exports = require(\"path\");;","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","// startup\n// Load entry module\n__webpack_require__(\"./electron/main.ts\");\n// This entry module used 'exports' so it can't be inlined\n"],"sourceRoot":""}
--------------------------------------------------------------------------------
/electron/main.ts:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow } from 'electron';
2 | import * as path from 'path';
3 |
4 | let mainWindow: Electron.BrowserWindow | null;
5 |
6 | function createWindow() {
7 | mainWindow = new BrowserWindow({
8 | width: 800,
9 | height: 700,
10 | // backgroundColor: "#45aaf2",
11 | webPreferences: {
12 | nodeIntegration: true,
13 | },
14 | });
15 |
16 | if (process.env.NODE_ENV === 'development') {
17 | mainWindow.loadURL(`http://localhost:3000`);
18 | } else {
19 | mainWindow.loadURL(path.resolve(__dirname, '../index.html'));
20 | }
21 |
22 | mainWindow.on('closed', () => {
23 | mainWindow = null;
24 | });
25 | }
26 |
27 | app.on('ready', createWindow);
28 | app.allowRendererProcessReuse = true;
29 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const mongoose = require ('mongoose')
2 |
3 | const URI = "mongodb+srv://mongodb:mongoDb@cluster0.drnfb.mongodb.net/sample_airbnb?retryWrites=true&w=majority";
4 |
5 | mongoose.connect(URI, { useNewUrlParser: true, useUnifiedTopology: true }, () => {console.log('you are connected')});
6 | //checking to see if we can pull data from DB database.collection.find
7 | // console.log(sample_airbnb.collection.find("_id"))
8 |
9 | const db = mongoose.connection;
10 | const schema = new mongoose.Schema({
11 | name: String,
12 | summary: String
13 | })
14 |
15 | const reviews = mongoose.model('listingsAndReviews', schema)
16 |
17 | db.on('error', console.error.bind(console, 'connection error: '));
18 |
19 | db.once('open', function() {
20 | console.log('in db')
21 | // reviews.findOne({ name: 'Ribeira Charming Duplex'})
22 |
23 | })
24 |
25 | reviews.find({}, (err, result) => {
26 | if(err) {console.log('error')};
27 | console.log(result);
28 | }); // .find method?
29 |
30 | // name of the collection
31 |
32 | // mongoose.collection('listingsAndReviews').find({ name: 'Ribeira Charming Duplex' });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RedisRate",
3 | "version": "1.0.0",
4 | "description": "Visualization tool to track key Redis performance metrics",
5 | "author": {
6 | "name": "Matthew Marchand, Heidi Bang, Daeun Chung, Navi Dhillon",
7 | "email": "redisrate@gmail.com",
8 | "url": "http://redisrate.com"
9 | },
10 | "main": "./dist/main.js",
11 | "scripts": {
12 | "start": "electron-forge start",
13 | "test": "jest",
14 | "redis-rate": "NODE_ENV=development nodemon ./server/server.js & webpack serve --config webpack.react.config.js --mode development & webpack --config webpack.electron.config.js --mode development && electron-forge start",
15 | "package": "electron-forge package",
16 | "make": "electron-forge make"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/oslabs-beta/RedisRate.git"
21 | },
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/oslabs-beta/RedisRate.git"
25 | },
26 | "homepage": "https://github.com/oslabs-beta/RedisRate#readme",
27 | "dependencies": {
28 | "@material-ui/core": "^4.11.2",
29 | "@material-ui/icons": "^4.11.2",
30 | "@types/react": "^17.0.0",
31 | "@types/react-dom": "^17.0.0",
32 | "@types/webdriverio": "^5.0.0",
33 | "axios": "^0.21.1",
34 | "bcrypt": "^5.0.0",
35 | "body-parser": "^1.19.0",
36 | "chart.js": "^2.9.4",
37 | "dotenv": "^8.2.0",
38 | "electron-squirrel-startup": "^1.0.0",
39 | "express": "^4.17.1",
40 | "mongoose": "^5.11.11",
41 | "nodemon": "^2.0.7",
42 | "pg": "^8.5.1",
43 | "react": "^17.0.1",
44 | "react-chartjs-2": "^2.11.1",
45 | "react-dom": "^17.0.1",
46 | "react-router-dom": "^5.2.0",
47 | "redis": "^3.0.2",
48 | "redis-info": "^3.0.8",
49 | "typescript": "^4.1.3"
50 | },
51 | "devDependencies": {
52 | "@babel/core": "^7.12.10",
53 | "@babel/preset-env": "^7.12.11",
54 | "@babel/preset-react": "^7.12.10",
55 | "@babel/preset-typescript": "^7.12.7",
56 | "@electron-forge/cli": "^6.0.0-beta.54",
57 | "@electron-forge/maker-deb": "^6.0.0-beta.54",
58 | "@electron-forge/maker-rpm": "^6.0.0-beta.54",
59 | "@electron-forge/maker-squirrel": "^6.0.0-beta.54",
60 | "@electron-forge/maker-zip": "^6.0.0-beta.54",
61 | "@types/react-router-dom": "^5.1.7",
62 | "babel-loader": "^8.2.2",
63 | "css-loader": "^5.0.1",
64 | "electron": "^11.2.0",
65 | "html-webpack-plugin": "^4.5.1",
66 | "jest": "^26.6.3",
67 | "style-loader": "^2.0.0",
68 | "supertest": "^6.1.3",
69 | "webpack": "^5.14.0",
70 | "webpack-cli": "^4.3.1",
71 | "webpack-dev-server": "^3.11.2"
72 | },
73 | "jest": {
74 | "testEnvironment": "node",
75 | "coveragePathIgnorePatterns": [
76 | "/node_modules/"
77 | ],
78 | "moduleNameMapper": {
79 | "\\.(css|less)$": "/__mocks__/styleMock.js"
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Type of change
2 | - [ ] Bug fix (change which fixes an issue)
3 | - [ ] New Feature (change which adds functionality)
4 | - [ ] Refactor (change which changes the codebase without affecting its behavior)
5 | - [ ] Non-breaking change (fix or feature that would cause existing functionality to work as expected)
6 | - [ ] Breaking change (fix or feature that would cause existing functionality to __not__ work as expected)
7 |
8 | ## The problem you are solving
9 |
10 | ## Your solution to the problem
11 |
12 | ## Any notes on testing this functionality
13 |
14 | ## Screenshots
--------------------------------------------------------------------------------
/server/Controllers/loginController.js:
--------------------------------------------------------------------------------
1 | const db = require('../LoginModel.js');
2 | const theCrypt = require('bcrypt');
3 |
4 | const loginController = {}
5 |
6 | loginController.validateUser = (req, res, next) => {
7 | const { username, password } = req.body;
8 | const query = `SELECT pass FROM users WHERE users.user=$1`;
9 | db.query(query, [username])
10 | .then((result) => {
11 | console.log(result.rows[0].pass)
12 | // use bcrypt to compare the user's text password with their hash in the db
13 | theCrypt.compare(password, result.rows[0].pass)
14 | .then((response) => {
15 | // if there's not a match
16 | if (!response) {
17 | console.log('user hash unable to be validated');
18 | // return false to prevent user from being redirected
19 | res.locals.isUserLoggedIn = false;
20 | return next();
21 | }
22 | // if there is a match
23 | console.log('user validated');
24 | // send back true to redirect user to Navigation
25 | res.locals.isUserLoggedIn = true;
26 | return next();
27 | })
28 | })
29 | .catch((err) => {
30 | console.log('Error validating user: ' + err)
31 | })
32 | }
33 |
34 | loginController.addUser = (req, res, next) => {
35 | const { firstName, lastName, username, password } = req.body;
36 | const query = 'INSERT INTO users ("user", "pass", "lName", "fName") VALUES ($1, $2, $3, $4)';
37 | theCrypt.hash(password, 13, (err, hash) => {
38 | if (err) {
39 | console.log('error creating hash')
40 | return next(err);
41 | }
42 | // After the user's password has been encrypted, store in PSQL
43 | db.query(query, [username, hash, lastName, firstName])
44 | .then(() => {
45 | console.log('User Added!');
46 | res.locals.isUserLoggedIn = true;
47 | return next();
48 | })
49 | .catch(err => {
50 | console.log('Error adding user to db, ' + err);
51 | res.locals.isUserLoggedIn = false;
52 | return next();
53 | })
54 | });
55 | }
56 |
57 | module.exports = loginController;
--------------------------------------------------------------------------------
/server/Controllers/redisController.js:
--------------------------------------------------------------------------------
1 | const redis = require('redis');
2 |
3 | const redisController = {};
4 |
5 | redisController.redisConnect = (req, res, next) => {
6 | const { port, ipaddress, username, password } = req.body;
7 | console.log('In the controller:', port);
8 |
9 | const retry_strategy = (options) => {
10 | if (options.error && options.error.code === "ECONNREFUSED") {
11 | // End reconnecting on a specific error and flush all commands with
12 | // a individual error
13 | return new Error("The server refused the connection");
14 | }
15 | if (options.total_retry_time > 1000 * 60 * 60) {
16 | // End reconnecting after a specific timeout and flush all commands
17 | // with a individual error
18 | return new Error("Retry time exhausted");
19 | }
20 | if (options.attempt > 10) {
21 | // End reconnecting with built in error
22 | return undefined;
23 | }
24 | // reconnect after
25 | return Math.min(options.attempt * 100, 3000);
26 | }
27 |
28 | const RedisDB = redis.createClient({
29 | host: ipaddress,
30 | port: port,
31 | password: password,
32 | retry_strategy: retry_strategy
33 | });
34 |
35 | RedisDB.on('error', err => {
36 | res.locals.login = false;
37 | console.log('Error connecting to Redis client:' + err);
38 | })
39 |
40 | // PULL METRICS FROM CLIENT'S REDIS
41 | RedisDB.on('ready', () => {
42 | // check if connected
43 | const thePing = RedisDB.ping();
44 | console.log(thePing);
45 | // if successfully connected,
46 | // return true to isUserLoggedIn frontend state
47 | res.locals.login = true;
48 |
49 | // MEMORY
50 | res.locals.allMemory = RedisDB.server_info.total_system_memory_human;
51 | res.locals.usedMemory = RedisDB.server_info.used_memory_human;
52 | res.locals.memoryFragmentation = RedisDB.server_info.mem_fragmentation_ratio;
53 | res.locals.evictedKeys = RedisDB.server_info.evicted_keys;
54 |
55 | // LATENCY
56 | res.locals.opsPerSec = RedisDB.server_info.instantaneous_ops_per_sec;
57 | res.locals.totalConnections = RedisDB.server_info.total_connections_received;
58 | // calculate keyspace hits rate from hits and misses
59 | let hits = parseInt(RedisDB.server_info.keyspace_hits);
60 | let misses = parseInt(RedisDB.server_info.keyspace_misses);
61 | res.locals.keyspaceHits = RedisDB.server_info.keyspace_hits;
62 | res.locals.keyspaceMisses = RedisDB.server_info.keyspace_misses;
63 | res.locals.hitRate = (hits / (hits + misses));
64 |
65 | // THROUGHPUT
66 | res.locals.connectedClients = RedisDB.server_info.connected_clients;
67 | res.locals.blockedClients = RedisDB.server_info.blocked_clients;
68 | res.locals.connectedSlaves = RedisDB.server_info.connected_slaves;
69 | res.locals.keyspace = RedisDB.server_info.keyspace;
70 | res.locals.totalCommands = RedisDB.server_info.total_commands_processed;
71 |
72 |
73 |
74 | return next();
75 | })
76 | };
77 |
78 | module.exports = redisController;
79 |
80 |
--------------------------------------------------------------------------------
/server/LoginModel.js:
--------------------------------------------------------------------------------
1 | const { Pool } = require('pg');
2 | require('dotenv').config()
3 |
4 | const PG_URI = process.env.PG_URI;
5 |
6 | const pool = new Pool({
7 | connectionString: PG_URI
8 | });
9 |
10 | module.exports = pool;
11 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const app = express();
3 | const bodyParser = require('body-parser');
4 | const PORT = 4000;
5 | const path = require('path');
6 | const redisController = require('./Controllers/redisController')
7 | const loginController = require('./Controllers/loginController');
8 |
9 | app.use(bodyParser.json());
10 |
11 | app.post('/login',
12 | loginController.validateUser,
13 | (req, res) => {
14 | res.status(200).json({ isUserLoggedIn: res.locals.isUserLoggedIn })
15 | }
16 | )
17 |
18 | app.post('/signup',
19 | loginController.addUser,
20 | (req, res) => {
21 | res.status(200).json({ isUserLoggedIn: res.locals.isUserLoggedIn });
22 | }
23 | )
24 |
25 | app.post('/connect',
26 | redisController.redisConnect,
27 | (req, res) => {
28 | res.status(200).json({
29 | login: res.locals.login,
30 | allMemory: res.locals.allMemory,
31 | usedMemory: res.locals.usedMemory,
32 | memoryFragRatio: res.locals.memoryFragmentation,
33 | evictedKeys: res.locals.evictedKeys,
34 | opsPerSec: res.locals.opsPerSec,
35 | keyspaceHits: res.locals.keyspaceHits,
36 | keyspaceMisses: res.locals.keyspaceMisses,
37 | hitRate: res.locals.hitRate,
38 | totalConnections: res.locals.totalConnections,
39 | connectedClients: res.locals.connectedClients,
40 | connectedSlaves: res.locals.connectedSlaves,
41 | totalCommands: res.locals.totalCommands,
42 | });
43 | }
44 | )
45 |
46 | app.get('/*', function (req, res) {
47 | res.sendFile(path.join(__dirname, '../index.html'), (err) => {
48 | if (err) {
49 | res.status(500).send(err)
50 | }
51 | })
52 | })
53 |
54 | app.listen(PORT, () => {
55 | console.log(`Listening on port: ${PORT}`)
56 | })
--------------------------------------------------------------------------------
/src/Connect.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useContext } from 'react';
2 | import AppContext from './context/index';
3 | import { useHistory } from 'react-router-dom';
4 |
5 | import './styles/styles.css';
6 | import TextField from '@material-ui/core/TextField';
7 | import Button from '@material-ui/core/Button';
8 |
9 | const Connect = () => {
10 |
11 | let history = useHistory();
12 | const {
13 | isDbConnected,
14 | setIsDbConnected,
15 | port,
16 | setPort,
17 | ipaddress,
18 | setIpaddress,
19 | password,
20 | setPassword,
21 | setMemoryData,
22 | setLatencyData,
23 | setThroughputData,
24 | } = useContext(AppContext);
25 |
26 | const onSubmit = (e) => {
27 | e.preventDefault();
28 | fetch('/connect', {
29 | body: JSON.stringify({
30 | port,
31 | ipaddress,
32 | password,
33 | }),
34 | method: 'POST',
35 |
36 | headers: { 'Content-Type': 'Application/JSON' },
37 | })
38 | .then(response => response.json())
39 | .then((response) => {
40 |
41 | const {
42 | login,
43 | allMemory,
44 | usedMemory,
45 | memoryFragRatio,
46 | evictedKeys,
47 | opsPerSec,
48 | keyspaceHits,
49 | keyspaceMisses,
50 | hitRate,
51 | totalCommands,
52 | totalConnections,
53 | connectedClients,
54 | connectedSlaves,
55 | blockedClients
56 | } = response;
57 |
58 | // Save associated data in React Context state objects
59 | setMemoryData({
60 | all: allMemory,
61 | used: usedMemory,
62 | fragRatio: memoryFragRatio,
63 | evictedKeys: evictedKeys,
64 | })
65 | setLatencyData({
66 | opsPerSec: opsPerSec,
67 | keyspaceHits: keyspaceHits,
68 | keyspaceMisses: keyspaceMisses,
69 | hitRate: hitRate,
70 | totalConnections: totalConnections,
71 | })
72 | setThroughputData({
73 | connectedClients: connectedClients,
74 | connectedSlaves: connectedSlaves,
75 | blockedClients: blockedClients,
76 | totalCommands: totalCommands,
77 | })
78 |
79 | /*
80 | Handle boolean to track user login status,
81 | this is linked to successful client connection with Redis
82 | */
83 | if (login) {
84 | setIsDbConnected(true);
85 | }
86 | if (!login) {
87 | setIsDbConnected(false);
88 | alert('We were unable to connect to your Redis Database, please ensure your server is up and running and your login information is correct and try again!');
89 | console.log('Invalid Login');
90 | }
91 | })
92 | .catch((err) => {
93 | console.log('could not send user info:', err);
94 | });
95 |
96 | };
97 |
98 | // Redirect user to navigation once they are authenticated
99 | useEffect(() => {
100 | if (isDbConnected) {
101 | history.push('/navigation');
102 | }
103 | }, [isDbConnected]);
104 |
105 | return (
106 |
116 | );
117 | };
118 |
119 | export default Connect;
120 |
--------------------------------------------------------------------------------
/src/Home.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import AppContext from "./context/index";
3 |
4 | const Home = () => {
5 |
6 | const {port, ipaddress} = useContext(AppContext);
7 |
8 |
9 | return (
10 |
11 |
Your Redis instance is running at
12 | Port: {port}
13 | IP Address: {ipaddress}
14 |
15 |
16 | )
17 | }
18 |
19 | export default Home;
--------------------------------------------------------------------------------
/src/Login.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useContext } from 'react';
2 | import AppContext from './context/index';
3 | import Connect from './Connect.jsx';
4 | import {
5 | BrowserRouter as Router,
6 | Switch,
7 | Route,
8 | Link,
9 | useHistory,
10 | } from 'react-router-dom';
11 |
12 | import './styles/styles.css';
13 | import TextField from '@material-ui/core/TextField';
14 | import Button from '@material-ui/core/Button';
15 |
16 |
17 | const Login = () => {
18 |
19 | let history = useHistory();
20 | const { isUserLoggedIn, setIsUserLoggedIn } = useContext(AppContext);
21 | const { username, setUsername } = useContext(AppContext);
22 | const { password, setPassword } = useContext(AppContext);
23 |
24 | const onSubmit = (e) => {
25 | e.preventDefault();
26 | fetch('/login', {
27 | body: JSON.stringify({
28 | username,
29 | password,
30 | }),
31 | method: 'POST',
32 | headers: { 'Content-Type': 'Application/JSON' },
33 | })
34 | .then(response => response.json())
35 | .then(res => {
36 | // check status code of 200 ?
37 | if (res["isUserLoggedIn"]){
38 | setIsUserLoggedIn(true);
39 | } else alert("Username/Password are incorrect. Please try again, or Sign up Now!");
40 | })
41 | .catch(err => console.log('error username or password does not exist: ', err))
42 | }
43 |
44 | function signUpNow() {
45 | history.push('/signup');
46 | }
47 |
48 | useEffect(() => {
49 | if (isUserLoggedIn) {
50 | history.push('/connect');
51 | }
52 | }, [isUserLoggedIn]);
53 |
54 | return (
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
})
63 |
64 |
70 |
71 |
72 |
73 |
74 | );
75 | };
76 |
77 | export default Login;
78 |
--------------------------------------------------------------------------------
/src/Metrics/Latency.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import '../styles/styles.css';
3 | import { Bar } from 'react-chartjs-2';
4 | import AppContext from '../context/index';
5 |
6 | const Latency = () => {
7 |
8 | const {
9 | latencyData,
10 | } = useContext(AppContext);
11 |
12 | if (latencyData.keyspaceHits === null) {
13 | latencyData.keyspaceHits = 0;
14 | }
15 |
16 | const data1 = {
17 | labels: ['Activity'],
18 | datasets: [
19 | {
20 | label: 'Operations Per Second',
21 | data: [latencyData.opsPerSec],
22 | backgroundColor: [
23 | 'rgba(255,143,229,1)',
24 | ],
25 | borderColor: [
26 | 'rgba(0,0,0)',
27 | ],
28 | borderWidth: 1,
29 | },
30 | {
31 | label: 'Total Connections',
32 | data: [latencyData.totalConnections],
33 | backgroundColor: [
34 | 'rgba(255,89,144,1)'
35 | ],
36 | borderColor: [
37 | 'rgba(0,0,0)'
38 | ],
39 | borderWidth: 1,
40 | }
41 | ],
42 | }
43 |
44 | const data2 = {
45 | labels: ['Keyspace'],
46 | datasets: [
47 | {
48 | label: 'Keyspace Hits',
49 | data: [latencyData.keyspaceHits],
50 | backgroundColor: [
51 | 'rgba(255,252,89,1)'
52 | ],
53 | borderColor: [
54 | 'rgba(0,0,0)'
55 | ],
56 | borderWidth: 1,
57 | },
58 | {
59 | label: 'Keyspace Misses',
60 | data: [latencyData.keyspaceMisses],
61 | backgroundColor: [
62 | 'rgba(186,255,89,1)'
63 | ],
64 | borderColor: [
65 | 'rgba(0,0,0)'
66 | ],
67 | borderWidth: 1,
68 | },
69 | {
70 | label: 'Keyspace Hit Rate',
71 | data: [latencyData.hitRate],
72 | backgroundColor: [
73 | 'rgba(89,255,227,1)'
74 | ],
75 | borderColor: [
76 | 'rgba(0,0,0)'
77 | ],
78 | borderWidth: 1,
79 | },
80 | ],
81 | }
82 |
83 | const options = {
84 | legend: {
85 | display: true,
86 | position: 'bottom',
87 | },
88 | scales: {
89 | yAxes: [
90 | {
91 | ticks: {
92 | beginAtZero: true,
93 | },
94 | },
95 | ],
96 | },
97 | }
98 |
99 | return (
100 |
101 |
107 |
113 |
114 | );
115 | };
116 |
117 | export default Latency;
--------------------------------------------------------------------------------
/src/Metrics/Memory.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import AppContext from '../context/index';
3 | import { Pie, Bar } from 'react-chartjs-2';
4 | import '../styles/styles.css';
5 |
6 | const Memory = () => {
7 |
8 | const {
9 | memoryData,
10 | } = useContext(AppContext);
11 |
12 | // convert string values returned from redisDB
13 | let totalMemory = memoryData.all;
14 | let usedMemory = memoryData.used;
15 |
16 | const tth = totalMemory[totalMemory.length - 1];
17 | const uth = usedMemory[usedMemory.length - 1];
18 | totalMemory.replace(tth, '');
19 | usedMemory.replace(uth, '');
20 | totalMemory = parseInt(totalMemory, 10);
21 | usedMemory = parseInt(usedMemory, 10);
22 | if (uth === 'M') {
23 | usedMemory = usedMemory / 1000;
24 | }
25 | if (uth === 'K') {
26 | usedMemory = usedMemory / 1000000
27 | }
28 |
29 | const pieData = {
30 | labels: ['Used Memory', 'Total Memory'],
31 | datasets: [
32 | {
33 | label: 'Used',
34 | backgroundColor: ['rgba(207,255,94,1)', 'rgba(61,252,255,1)'],
35 | borderColor: 'rgba(0,0,0)',
36 | borderWidth: 1,
37 | data: [usedMemory, totalMemory]
38 | },
39 | ]
40 | }
41 |
42 | return (
43 |
44 |
You are using {memoryData.used} out of {memoryData.all} available memory
45 |
48 |
Memory fragmentation ratio: {memoryData.fragRatio}
49 |
Evicted Keys: {memoryData.evictedKeys}
50 |
51 | );
52 | };
53 |
54 | export default Memory;
--------------------------------------------------------------------------------
/src/Metrics/Throughput.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import AppContext from '../context/index';
3 | import { Bar } from 'react-chartjs-2';
4 | import '../styles/styles.css';
5 |
6 | const Throughput = () => {
7 |
8 | const {
9 | throughputData,
10 | } = useContext(AppContext);
11 |
12 | const state = {
13 | datasets: [
14 | {
15 | label: 'Connected Clients',
16 | backgroundColor: 'rgba(255, 99, 132, 1)',
17 | borderColor: 'rgba(0,0,0)',
18 | borderWidth: 2,
19 | data: [throughputData.connectedClients]
20 | },
21 | {
22 | label: 'Connected Replicas',
23 | backgroundColor: 'rgba(54, 162, 235, 1)',
24 | borderColor: 'rgba(0,0,0)',
25 | borderWidth: 2,
26 | data: [throughputData.connectedSlaves]
27 | },
28 | {
29 | label: 'Blocked Clients',
30 | backgroundColor: 'rgba(255,178,89,1)',
31 | borderColor: 'rgba(0,0,0)',
32 | borderWidth: 2,
33 | data: [throughputData.blockedClients]
34 | }
35 | ]
36 | }
37 |
38 | return (
39 |
40 |
Total Commands Processed: {throughputData.totalCommands}
41 |
50 |
51 | )
52 | }
53 |
54 | export default Throughput;
--------------------------------------------------------------------------------
/src/Navigation.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState, useEffect } from 'react';
2 | import { useHistory } from 'react-router-dom';
3 | import AppContext from './context/index';
4 |
5 | // material UI imports for navbar
6 | import clsx from 'clsx';
7 | import { makeStyles, useTheme, Theme, createStyles, withStyles } from '@material-ui/core/styles';
8 | import Drawer from '@material-ui/core/Drawer';
9 | import CssBaseline from '@material-ui/core/CssBaseline';
10 | import AppBar from '@material-ui/core/AppBar';
11 | import Toolbar from '@material-ui/core/Toolbar';
12 | import List from '@material-ui/core/List';
13 | import Typography from '@material-ui/core/Typography';
14 | import Divider from '@material-ui/core/Divider';
15 | import IconButton from '@material-ui/core/IconButton';
16 | import MenuIcon from '@material-ui/icons/Menu';
17 | import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
18 | import ListItem from '@material-ui/core/ListItem';
19 | import ListItemText from '@material-ui/core/ListItemText';
20 |
21 | import Memory from './Metrics/Memory.jsx';
22 | import Home from './Home.jsx';
23 | import Latency from './Metrics/Latency.jsx'
24 | import Throughput from './Metrics/Throughput.jsx';
25 |
26 |
27 | const drawerWidth = 240;
28 |
29 | const useStyles = makeStyles((theme) =>
30 | createStyles({
31 | root: {
32 | display: 'flex',
33 | },
34 | appBar: {
35 | transition: theme.transitions.create(['margin', 'width'], {
36 | easing: theme.transitions.easing.sharp,
37 | duration: theme.transitions.duration.leavingScreen,
38 | }),
39 | },
40 | appBarShift: {
41 | width: `calc(100% - ${drawerWidth}px)`,
42 | marginLeft: drawerWidth,
43 | transition: theme.transitions.create(['margin', 'width'], {
44 | easing: theme.transitions.easing.easeOut,
45 | duration: theme.transitions.duration.enteringScreen,
46 | }),
47 | },
48 | menuButton: {
49 | marginRight: theme.spacing(2),
50 | },
51 | hide: {
52 | display: 'none',
53 | },
54 | drawer: {
55 | width: drawerWidth,
56 | flexShrink: 0,
57 | },
58 | drawerPaper: {
59 | width: drawerWidth,
60 | },
61 | drawerHeader: {
62 | display: 'flex',
63 | backgroundColor: 'transparent',
64 | alignItems: 'center',
65 | padding: theme.spacing(0, 1),
66 | // necessary for content to be below app bar
67 | ...theme.mixins.toolbar,
68 | justifyContent: 'flex-end',
69 | },
70 | content: {
71 | flexGrow: 1,
72 | padding: theme.spacing(3),
73 | transition: theme.transitions.create('margin', {
74 | easing: theme.transitions.easing.sharp,
75 | duration: theme.transitions.duration.leavingScreen,
76 | }),
77 | marginLeft: -drawerWidth,
78 | },
79 | contentShift: {
80 | transition: theme.transitions.create('margin', {
81 | easing: theme.transitions.easing.easeOut,
82 | duration: theme.transitions.duration.enteringScreen,
83 | }),
84 | marginLeft: 0,
85 | },
86 | }),
87 | );
88 |
89 | const WhiteTextTypography = withStyles({
90 | root: {
91 | color: "white"
92 | }
93 | })(Typography);
94 |
95 | export default function Navigation() {
96 |
97 | let history = useHistory();
98 | const classes = useStyles();
99 | const theme = useTheme();
100 | const [open, setOpen] = React.useState(false);
101 |
102 | const [page, setPage] = useState('Home');
103 | const { setIsDbConnected } = useContext(AppContext);
104 | const { isUserLoggedIn, setIsUserLoggedIn } = useContext(AppContext);
105 |
106 | const handleDrawerOpen = () => {
107 | setOpen(true);
108 | };
109 |
110 | const handleDrawerClose = () => {
111 | setOpen(false);
112 | };
113 |
114 | const logout = () => {
115 | setIsUserLoggedIn(false)
116 | setIsDbConnected(false);
117 | }
118 |
119 | useEffect(() => {
120 | if (!isUserLoggedIn) {
121 | history.push('/');
122 | }
123 | }, [isUserLoggedIn]);
124 |
125 | return (
126 |
127 |
128 |
134 |
135 |
142 |
143 |
144 |
145 | {page}
146 |
147 |
148 |
149 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 | setPage('Memory')} button key='Memory'>
166 |
167 |
168 | setPage('Latency')} button key='Latency'>
169 |
170 |
171 | setPage('Throughput')} button key='Throughput'>
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
184 |
185 | {
186 | {
187 | Home: ,
188 | Memory: ,
189 | Latency: ,
190 | Throughput:
191 | }[page]
192 | }
193 |
194 |
195 | )
196 | }
197 |
--------------------------------------------------------------------------------
/src/SignUp.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useContext } from 'react';
2 | import AppContext from './context/index';
3 | import Memory from './Metrics/Memory.jsx';
4 | import {
5 | Switch,
6 | Route,
7 | useHistory,
8 | } from 'react-router-dom';
9 |
10 | import './styles/styles.css';
11 | import TextField from '@material-ui/core/TextField';
12 | import Button from '@material-ui/core/Button';
13 |
14 |
15 | const SignUp = () => {
16 |
17 | let history = useHistory();
18 | const { isUserLoggedIn, setIsUserLoggedIn } = useContext(AppContext);
19 |
20 | const { firstName, setFirstName } = useContext(AppContext);
21 | const { lastName, setLastName } = useContext(AppContext);
22 | const { username, setUsername } = useContext(AppContext);
23 | const { password, setPassword } = useContext(AppContext);
24 |
25 | const onSubmit = (e) => {
26 | e.preventDefault();
27 | fetch('/signup', {
28 | body: JSON.stringify({
29 | firstName,
30 | lastName,
31 | username,
32 | password,
33 | }),
34 | method: 'POST',
35 | headers: { 'Content-Type': 'Application/JSON' },
36 | })
37 | .then(response => response.json())
38 | .then(res => {
39 | if (res.isUserLoggedIn === true){
40 | setIsUserLoggedIn(true);
41 | }
42 | })
43 | .catch(err => console.log('error creating user: ', err))
44 | }
45 |
46 | useEffect(() => {
47 | if(isUserLoggedIn) {
48 | history.push('/connect'); //later, change this to general page
49 | }
50 | }, [isUserLoggedIn]);
51 |
52 |
53 | return (
54 |
55 |
56 |
57 |
})
58 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | );
74 | }
75 |
76 | export default SignUp;
77 |
--------------------------------------------------------------------------------
/src/app.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDom from 'react-dom';
3 | import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
4 | import Login from './Login.jsx';
5 | import SignUp from './SignUp.jsx';
6 | import Connect from './Connect.jsx';
7 | import Navigation from './Navigation.jsx';
8 | import ContextProvider from './context/ContextProvider';
9 | import { createMuiTheme, ThemeProvider, makeStyles } from '@material-ui/core/styles';
10 | import CssBaseline from '@material-ui/core/CssBaseline';
11 | import { colors } from '@material-ui/core';
12 |
13 |
14 | const theme = createMuiTheme ({
15 | palette: {
16 | primary : { main: '#5bbd96', contrastText: '#fff'},
17 | secondary: { main: '#ffffff'},
18 | background : { paper : '#353552', default : '#353552'},
19 | text : { primary : '#FFFFFF', secondary: '#bdbbc7'},
20 |
21 | }
22 | });
23 |
24 |
25 | const mainElement = document.createElement('div');
26 | document.body.appendChild(mainElement);
27 | document.title = 'Redis Rate';
28 |
29 | const App = () => {
30 | return (
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | ReactDom.render(
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | ,
61 | mainElement
62 | );
63 |
--------------------------------------------------------------------------------
/src/context/ContextProvider.js:
--------------------------------------------------------------------------------
1 | /*
2 | React Context API used for state management
3 | Context object here hold state variables and methods
4 | */
5 |
6 | import React, { Provider, useState } from 'react';
7 | import AppContext from './index.ts';
8 |
9 | const ContextProvider = ({ children }) => {
10 |
11 | const [port, setPort] = useState('');
12 | const [ipaddress, setIpaddress] = useState('');
13 | const [firstName, setFirstName] = useState('');
14 | const [lastName, setLastName] = useState('');
15 | const [username, setUsername] = useState('');
16 | const [password, setPassword] = useState('');
17 | const [serverPassword, setServerPassword] = useState('');
18 | const [memoryData, setMemoryData] = useState({});
19 | const [throughputData, setThroughputData] = useState({});
20 | const [latencyData, setLatencyData] = useState({});
21 | const [isDbConnected, setIsDbConnected] = useState(false);
22 | const [isUserLoggedIn, setIsUserLoggedIn ] = useState(false);
23 | const [page, setPage] = useState('Home');
24 |
25 | const context = {
26 | setPort,
27 | port,
28 | setIpaddress,
29 | ipaddress,
30 | setFirstName,
31 | firstName,
32 | setLastName,
33 | lastName,
34 | setUsername,
35 | username,
36 | setPassword,
37 | password,
38 | setServerPassword,
39 | serverPassword,
40 | setMemoryData,
41 | memoryData,
42 | setThroughputData,
43 | throughputData,
44 | setLatencyData,
45 | latencyData,
46 | setIsDbConnected,
47 | isDbConnected,
48 | setPage,
49 | page,
50 | setIsUserLoggedIn,
51 | isUserLoggedIn,
52 | };
53 |
54 | return {children};
55 | };
56 |
57 | export default ContextProvider;
58 |
--------------------------------------------------------------------------------
/src/context/index.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | const AppContext = React.createContext({});
3 | export default AppContext;
4 |
--------------------------------------------------------------------------------
/src/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Redis Rate
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/styles/assets/connection.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/RedisRate/aff10f41762557aec13b06c84203f201a1da1f4c/src/styles/assets/connection.gif
--------------------------------------------------------------------------------
/src/styles/assets/git.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/RedisRate/aff10f41762557aec13b06c84203f201a1da1f4c/src/styles/assets/git.png
--------------------------------------------------------------------------------
/src/styles/assets/latency.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/RedisRate/aff10f41762557aec13b06c84203f201a1da1f4c/src/styles/assets/latency.gif
--------------------------------------------------------------------------------
/src/styles/assets/linkedin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/RedisRate/aff10f41762557aec13b06c84203f201a1da1f4c/src/styles/assets/linkedin.png
--------------------------------------------------------------------------------
/src/styles/assets/login.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/RedisRate/aff10f41762557aec13b06c84203f201a1da1f4c/src/styles/assets/login.gif
--------------------------------------------------------------------------------
/src/styles/assets/logout.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/RedisRate/aff10f41762557aec13b06c84203f201a1da1f4c/src/styles/assets/logout.gif
--------------------------------------------------------------------------------
/src/styles/assets/memory.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/RedisRate/aff10f41762557aec13b06c84203f201a1da1f4c/src/styles/assets/memory.gif
--------------------------------------------------------------------------------
/src/styles/assets/redisrate1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/RedisRate/aff10f41762557aec13b06c84203f201a1da1f4c/src/styles/assets/redisrate1.png
--------------------------------------------------------------------------------
/src/styles/assets/signup.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/RedisRate/aff10f41762557aec13b06c84203f201a1da1f4c/src/styles/assets/signup.gif
--------------------------------------------------------------------------------
/src/styles/assets/throughput.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/RedisRate/aff10f41762557aec13b06c84203f201a1da1f4c/src/styles/assets/throughput.gif
--------------------------------------------------------------------------------
/src/styles/styles.css:
--------------------------------------------------------------------------------
1 | #loginPage {
2 | width: 100%;
3 | height: 100%;
4 | padding: 0;
5 | display: block;
6 | margin: auto;
7 | max-height: 300px;
8 | max-width: 300px;
9 | align-items: center;
10 | }
11 |
12 | #loginform {
13 | margin: 15px;
14 | display: flex;
15 | flex-direction: column;
16 | color: rgb(214, 209, 209);
17 | }
18 |
19 | #connectPage {
20 | width: 100%;
21 | height: 100%;
22 | padding: 0;
23 | margin: auto;
24 | max-height: 300px;
25 | max-width: 300px;
26 | align-items: center;
27 | }
28 |
29 | #connectform {
30 | margin: 15px;
31 | display: flex;
32 | flex-direction: column;
33 | }
34 | #submitbutt {
35 | margin-top: 15px;
36 | padding: 15px;
37 | border-radius: 15px;
38 | }
39 | #submitbutt:hover {
40 | /* background-color: #f3b4ca; */
41 | /* opacity: 10%; */
42 | color: rgb(139, 226, 226);
43 | border: 1px solid rgb(139, 226, 226);
44 | }
45 |
46 | #loginbutt {
47 | margin-top: 15px;
48 | padding: 15px;
49 | border-radius: 15px;
50 | }
51 | #loginbutt:hover {
52 | color: rgb(139, 226, 226);
53 | border: 1px solid rgb(139, 226, 226);
54 | }
55 |
56 | img {
57 | display: flex;
58 | justify-content: center;
59 | align-items: center;
60 | margin-left: 50px;
61 | }
62 |
63 | #signup {
64 | display: flex;
65 | flex-direction: column;
66 | align-items: center;
67 | }
68 |
69 | #signupForm {
70 | display: flex;
71 | flex-direction: column;
72 | align-items: center;
73 | }
74 |
75 | #form {
76 | margin: 15px;
77 | display: flex;
78 | justify-content: center;
79 | align-items: center;
80 | }
81 |
82 | #homeFont {
83 | font-family: 'Ubuntu', sans-serif;
84 | font-style: italic;
85 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "lib": [
6 | "dom",
7 | "ES2015",
8 | "ES2016",
9 | "ES2017"
10 | ],
11 | "allowJs": true,
12 | "jsx": "react",
13 | "sourceMap": true,
14 | "outDir": "./dist",
15 | "strict": true,
16 | "esModuleInterop": true,
17 | }
18 | }
--------------------------------------------------------------------------------
/webpack.electron.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | resolve: {
5 | extensions: ['.tsx', '.ts', '.js', '.jsx'],
6 | },
7 | devtool: 'source-map',
8 | entry: './electron/main.ts',
9 | target: 'electron-main',
10 | module: {
11 | rules: [
12 | {
13 | test: /\.(js|ts|tsx|jsx)$/,
14 | exclude: /node_modules/,
15 | use: {
16 | loader: 'babel-loader',
17 | },
18 | },
19 | ],
20 | },
21 | output: {
22 | path: path.resolve(__dirname, './dist'),
23 | filename: '[name].js',
24 | },
25 | };
--------------------------------------------------------------------------------
/webpack.react.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | module.exports = {
5 | resolve: {
6 | extensions: ['.tsx', '.ts', '.js', 'jsx'],
7 | mainFields: ['main', 'module', 'browser'],
8 | },
9 | entry: './src/app.jsx',
10 | target: 'electron-renderer',
11 | devtool: 'source-map',
12 | module: {
13 | rules: [
14 | {
15 | test: /\.(js|ts|tsx|jsx)$/,
16 | exclude: /node_modules/,
17 | use: {
18 | loader: 'babel-loader',
19 | },
20 | },
21 | {
22 | test: /\.css$/i,
23 | exclude: /node_modules/,
24 | use: ['style-loader', 'css-loader'],
25 | },
26 | {
27 | test: /\.png$/i,
28 | type: "asset/resource"
29 | }
30 | ],
31 | },
32 | devServer: {
33 | contentBase: path.join(__dirname, '../dist/renderer'),
34 | historyApiFallback: true,
35 | compress: true,
36 | hot: true,
37 | port: 3000,
38 | publicPath: '/',
39 | proxy: { '*': { target: 'http://localhost:4000' } },
40 | historyApiFallback: true,
41 | },
42 | output: {
43 | path: path.resolve(__dirname, '../dist/renderer'),
44 | filename: 'js/[name].js',
45 | publicPath: './',
46 | },
47 | plugins: [new HtmlWebpackPlugin()],
48 | };
49 |
--------------------------------------------------------------------------------