├── .babelrc ├── .eslintrc.yml ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── README.md ├── babelconfigtest ├── coverage ├── clover.xml ├── coverage-final.json ├── lcov-report │ ├── base.css │ ├── block-navigation.js │ ├── favicon.png │ ├── hyperion │ │ ├── firebase.js.html │ │ ├── index.html │ │ └── src │ │ │ ├── AuthContext.js.html │ │ │ ├── Login.js.html │ │ │ └── index.html │ ├── index.html │ ├── prettify.css │ ├── prettify.js │ ├── sort-arrow-sprite.png │ └── sorter.js └── lcov.info ├── dist ├── bundle.js ├── bundle.js.LICENSE.txt ├── index.html └── src │ └── assets │ └── Hyperion.png ├── firebase.js ├── package-lock.json ├── package.json ├── server ├── Controllers │ ├── metricController.js │ └── userController.js ├── models │ ├── errorLog.js │ └── users.js ├── routers │ └── Router.js └── server.js ├── src ├── App.tsx ├── Components │ ├── ActiveControllers.jsx │ ├── AvgRequestLatency.jsx │ ├── BytesConsumedRate.jsx │ ├── DataContainer.jsx │ ├── ErrorLogContainer.jsx │ ├── NavBar.tsx │ ├── OfflinePartitions.jsx │ ├── OutgoingByteRate.jsx │ ├── RequestRate.jsx │ ├── ResponseRate.jsx │ ├── SideNav.tsx │ ├── SimpleKeyMetrics.jsx │ └── UnderReplicated.jsx ├── Pages │ ├── AuthContext.jsx │ ├── ErrorLogDisplay.jsx │ ├── ForgotPassword.jsx │ ├── Login.jsx │ ├── MainDisplay.jsx │ ├── PrivateRoute.jsx │ └── Signup.jsx ├── app.scss ├── assets │ ├── Hyperion.png │ ├── LoginLogo.png │ ├── SpaceLogo.png │ ├── connect.png │ ├── errorlog.png │ ├── login.png │ ├── moredash.png │ └── whitelogo.png ├── index.html ├── index.tsx ├── setuptTest.js └── test │ ├── AvgRequestLatency.test.jsx │ ├── Login.test.jsx │ ├── OfflinePartitions.test.jsx │ └── SignUp.test.jsx ├── tsconfig.json ├── vite.config.ts └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "presets": [ "@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"], 4 | "plugins": [] 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | es2021: true 4 | extends: 5 | - eslint:recommended 6 | - plugin:react/recommended 7 | - plugin:@typescript-eslint/recommended 8 | overrides: [] 9 | parser: '@typescript-eslint/parser' 10 | parserOptions: 11 | ecmaVersion: latest 12 | sourceType: module 13 | plugins: 14 | - react 15 | - '@typescript-eslint' 16 | rules: {} -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | ./dist/bundle.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Tutorial 2 |
3 | 4 | Logo 5 | 6 |

Hyperion

7 |

An open-source Kafka monitoring tool built for developers

8 | 9 | GitHub stars 10 | GitHub issues 11 | GitHub last commit 12 | 13 | hyperionapp.dev 14 |

15 | 16 | ## Table of Contents 17 | 18 | 1. [About the Project](#about-the-project) 19 | - [Built With](#built-with) 20 | 1. [Getting Started](#getting-started) 21 | - [Requirements](#requirements) 22 | - [Installation](#installation) 23 | - [Usage](#when-you're-ready-to-use-hyperion) 24 | 1. [Demo](#demo) 25 | 1. [Roadmap](#roadmap) 26 | 1. [Contributors](#contributors) 27 | 1. [Support the Project](#support-the-project) 28 | 1. [License](#license) 29 | 30 | ## About the Project 31 | 32 | Hyperion is an open-source visualization tool for monitoring crucial health metrics of your Kafka cluster. This tool allows developers to quickly assess the functionality of their Kafka cluster, as well as identify metrics that become out of the appropriate range. Real-time metrics are consolidated in one dashboard for easy access, and developers have access to an error log where instances of out-of-range metrics are displayed. This application can be deployed on your local network. 33 | 34 | Featured metrics include: under-replicated partitions, offline partitions, active controller count, producer average request latency, request rate, response rate, bytes consumed rate, and producer outgoing byte rate. 35 | 36 | ### Built With 37 | 38 | - [Chart.js](https://www.chartjs.org/) 39 | - [React](https://reactjs.org/) 40 | - [Material-UI](https://mui.com/) 41 | - [FireBase SDK Authentication](https://firebase.google.com/docs/auth) 42 | - [PromQL](https://prometheus.io/) 43 | - [Node.js/Express](https://expressjs.com/) 44 | - [Socket.io](https://socket.io/) 45 | - [Vite.js](https://vitejs.dev/) 46 | 47 | 48 | ## Getting Started 49 | 50 | ### Requirements 51 | 52 | 1. You will need npm and the latest version of Node.js. 53 | 54 | 2. Ports 3001 and 8080 need to be available to run this application. 55 | 56 | 3. Make sure you have a running Kafka cluster with a configured Prometheus instance (you will need the domain and port of where Prometheus is running) 57 | 58 | ### Installation 59 | 60 | 1. Clone this repository in your local machine: 61 | 62 | ``` 63 | git clone https://github.com/oslabs-beta/hyperionn 64 | ``` 65 | 66 | 2. Install all dependencies: 67 | 68 | ``` 69 | npm install 70 | ``` 71 | 72 | 4. Build your version of Hyperion: 73 | 74 | ``` 75 | npm run build 76 | ``` 77 | 78 | ### When you're ready to use Hyperion 79 | 80 | 1. Start the server: 81 | 82 | ``` 83 | npm start 84 | ``` 85 | 86 | 2. Hyperion defaults to running on port 3000. Simply go to http://localhost:3000 to view your metrics and start managing your Kafka cluster! 87 | 88 | 3. After you log in, click ‘connect’ in the side navbar. Input the port number and the domain where your prometheus instance is running for your Kafka cluster and click ‘submit’. 89 | 90 | 4. Congratulations! You can now view live streaming of data. 91 | 92 | Note: When the simple key metrics (active controllers, under-replicated partitions, offline partitions) are not their expected values, the app will store the occurrence and display them in the error log found on the side navbar. 93 | 94 | 95 | ## Demo 96 |
97 | LoginScreenshot 98 |
99 | 1. Login with traditional sign up or Google/Github SDK authentication. 100 | 101 |
102 | ConnectScreenshot 103 |
104 | 2. Connect to your Prometheus domain/IP and port. 105 | 106 |
107 | DashboardScreenshot 108 |
109 | 3. Your dashboard will populate with the metrics after connecting. 110 | 111 |
112 | ErrorLogScreenshot 113 |
114 | 4. Utilize the Error Logs to view occurences of out of range metrics. 115 | 116 | 117 | 118 | ## Roadmap 119 | 120 | Here are some features the Hyperion team is working on adding to the application in the near future: 121 | 122 | - Additional metrics to view monitor performance 123 | - Ability to customize dashboard with metrics that are important to your Kafka cluster 124 | - End-to-end testing with Cypress 125 | - Refactoring codebase to Typescript for static testing 126 | 127 | If there is a feature you think would be useful to you and your team, or if you find any bugs, please [open an issue](https://github.com/oslabs-beta/hyperionn/issues). 128 | 129 | 130 | ## Contributors 131 | 132 | - Anish Patel | [GitHub](https://github.com/justanotherguyonline) | [Linkedin](https://www.linkedin.com/in/anish-patel-759545123/) 133 | - Kristin Green | [GitHub](https://github.com/kngreen) | [Linkedin](https://www.linkedin.com/in/kristin-green-101902a4/) 134 | - Joey Friedman | [GitHub](https://github.com/fried-jo) | [Linkedin](https://www.linkedin.com/in/joseph-friedman-803803149/) 135 | - Anita Duong | [GitHub](https://github.com/anitaduong98) | [Linkedin](https://www.linkedin.com/in/anita-duong/) 136 | 137 | 138 | ## Support the Project 139 | 140 | Contributions are welcomed and appreciated. You can do the following to support the project! 141 | 142 | - Star this repository 143 | - Raise new issues 144 | - Fork, clone, and make a PR to solve an issue 145 | - Check out our [Medium article](https://medium.com/@friedman.joey/hyperion-take-control-over-the-health-of-your-kafka-clusters-796eb061b53c) 146 | 147 | 148 | ## License 149 | 150 | This product is licensed under the MIT License without restriction. 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /babelconfigtest: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ '@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'], 3 | env: { 4 | test: { 5 | plugins: ["@babel/plugin-transform-modules-commonjs"] 6 | } 7 | } 8 | };@@ -------------------------------------------------------------------------------- /coverage/clover.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /coverage/coverage-final.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /coverage/lcov-report/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Hyperionn/0f56d421def6c3ffcf09bb946ebc69aa4a63b764/coverage/lcov-report/favicon.png -------------------------------------------------------------------------------- /coverage/lcov-report/hyperion/firebase.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for hyperion/firebase.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / hyperion firebase.js

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 4/4 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 | 4/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 | 27 93 | 28 94 | 29 95 | 30 96 | 31 97 | 32 98 | 33 99 | 34 100 | 35 101 | 36 102 | 37 103 | 38 104 | 39 105 | 40 106 | 41 107 | 42 108 | 43 109 | 44 110 | 45 111 | 46 112 | 47 113 | 48 114 | 49 115 | 50 116 | 51 117 | 52 118 | 53 119 | 54 120 | 55 121 | 56 122 | 57 123 | 58 124 | 59 125 | 60 126 | 61 127 | 62 128 | 63 129 | 64  130 |   131 |   132 |   133 |   134 |   135 |   136 |   137 |   138 |   139 |   140 |   141 |   142 |   143 |   144 |   145 |   146 |   147 |   148 |   149 |   150 |   151 |   152 |   153 |   154 |   155 |   156 |   157 |   158 | 1x 159 |   160 |   161 |   162 |   163 |   164 |   165 |   166 |   167 |   168 |   169 |   170 |   171 |   172 |   173 |   174 |   175 |   176 |   177 | 1x 178 |   179 |   180 |   181 |   182 |   183 |   184 |   185 |   186 |   187 |   188 | 1x 189 |   190 | 1x 191 |   192 |  
// Import the functions you need from the SDKs you need
193 |  
194 | import { initializeApp } from "firebase/app";
195 | // import { getAnalytics } from "firebase/analytics";
196 | //import firebase from  'firebase/compat/app' 
197 |  
198 | //import "firebase/auth";
199 | //{ getAuth, onAuthStateChanged }
200 | //import firebase from "./firebase";
201 | // import firebase from 'firebase/compat/app';
202 | // import { getAuth, signInWithPopup, GoogleAuthProvider } from 'firebase/compat/auth';
203 | // import 'firebase/compat/firestore';
204 | // import firebase from 'firebase/compat/app';
205 |  
206 | // import 'firebase/compat/auth';
207 | // import 'firebase/compat/database';
208 |  
209 | // import "firebase/compat/app";
210 | // import "firebase/compat/analytics";
211 |  
212 | // import {getAuth, 
213 | //     signOut, 
214 | //     sendPasswordResetEmail,
215 | //      createUserWithEmailAndPassword,
216 | //      signInWithEmailAndPassword,
217 | //      signOut,
218 | //      se 
219 | //      } from "firebase/auth";
220 |  
221 | ""
222 | import * as firebase from 'firebase/auth';
223 |  
224 | // firebase.initializeApp(firebaseConfig);
225 |  
226 | // // Namespaced syntax requires compat version
227 | // //export const auth = firebase.auth();
228 | // export const database = firebase.database();
229 | // // Add the Firebase products that you want to use
230 | // //import "firebase/auth";
231 | // console.log('Firebase.auth: ', firebase.auth);
232 | // //import 'firebase/compat/auth';
233 |  
234 |  
235 | // TODO: Add SDKs for Firebase products that you want to use
236 | // https://firebase.google.com/docs/web/setup#available-libraries
237 |  
238 | // Your web app's Firebase configuration
239 | // For Firebase JS SDK v7.20.0 and later, measurementId is optional
240 | const firebaseConfig = {
241 |   apiKey: "AIzaSyBp29QyjHWW6Jojs2Hufn7z8zmNMVw5290",
242 |   authDomain: "hyperion-272ea.firebaseapp.com",
243 |   projectId: "hyperion-272ea",
244 |   storageBucket: "hyperion-272ea.appspot.com",
245 |   messagingSenderId: "90144758527",
246 |   appId: "1:90144758527:web:462b4eb5b5408c5e63caca",
247 |   measurementId: "G-3KM8KR7HS2"
248 | };
249 |  
250 | // Initialize Firebase
251 | const app = initializeApp(firebaseConfig);
252 | // console.log('app', app)
253 | export const auth = firebase.getAuth(app);
254 | // export default ;
255 | export default app;
256 | 257 |
258 |
259 | 264 | 265 | 270 | 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /coverage/lcov-report/hyperion/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for hyperion 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files hyperion

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 4/4 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 | 4/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 | 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
firebase.js 84 |
85 |
100%4/4100%0/0100%0/0100%4/4
98 |
99 |
100 |
101 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /coverage/lcov-report/hyperion/src/AuthContext.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for hyperion/src/AuthContext.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / hyperion/src AuthContext.js

23 |
24 | 25 |
26 | 13.04% 27 | Statements 28 | 3/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 | 13.04% 48 | Lines 49 | 3/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 | 
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 | 27 93 | 28 94 | 29 95 | 30 96 | 31 97 | 32 98 | 33 99 | 34 100 | 35 101 | 36 102 | 37 103 | 38 104 | 39 105 | 40 106 | 41 107 | 42 108 | 43 109 | 44 110 | 45 111 | 46 112 | 47 113 | 48 114 | 49 115 | 50 116 | 51 117 | 52 118 | 53 119 | 54 120 | 55 121 | 56 122 | 57 123 | 58 124 | 59 125 | 60 126 | 61 127 | 62 128 | 63 129 | 64 130 | 65 131 | 66 132 | 67 133 | 68 134 | 69 135 | 70 136 | 71 137 | 72 138 | 73 139 | 74 140 | 75 141 | 76 142 | 77 143 | 78 144 | 79 145 | 80 146 | 81 147 | 82 148 | 83 149 | 84 150 | 85 151 | 86 152 | 87  153 |   154 |   155 |   156 |   157 |   158 |   159 |   160 |   161 |   162 |   163 |   164 |   165 |   166 |   167 |   168 | 1x 169 | 1x 170 | 1x 171 |   172 |   173 |   174 |   175 |   176 |   177 |   178 |   179 |   180 |   181 |   182 |   183 |   184 |   185 |   186 |   187 |   188 |   189 |   190 |   191 |   192 |   193 |   194 |   195 |   196 |   197 |   198 |   199 |   200 |   201 |   202 |   203 |   204 |   205 |   206 |   207 |   208 |   209 |   210 |   211 |   212 |   213 |   214 |   215 |   216 |   217 |   218 |   219 |   220 |   221 |   222 |   223 |   224 |   225 |   226 |   227 |   228 |   229 |   230 |   231 |   232 |   233 |   234 |   235 |   236 |   237 |   238 |  
import React, { useContext, useState, useEffect, createContext } from "react"
239 | import { auth } from "../firebase.js"
240 | import app from '../firebase.js';
241 | import { ConstructionOutlined, Google } from "@mui/icons-material";
242 | import { getAuth, 
243 |     createUserWithEmailAndPassword, 
244 |     signInWithEmailAndPassword,
245 |      signOut, 
246 |      sendPasswordResetEmail,
247 |      updateEmail,
248 |      updatePassword,
249 |      onAuthStateChanged,
250 |     GoogleAuthProvider,
251 |     signInWithPopup,
252 |     GithubAuthProvider
253 |      } from "firebase/auth"
254 | const goog = new GoogleAuthProvider()
255 | const github = new GithubAuthProvider();
256 | const AuthContext = React.createContext()
257 | //const auth = getAuth()
258 | export function useAuth() {
259 |   return useContext(AuthContext)
260 | }
261 |  
262 | export function AuthProvider({ children }) {
263 |   const [currentUser, setCurrentUser] = useState()
264 |   const [loading, setLoading] = useState(true)
265 |  
266 |   function signup(email, password) {
267 |     return createUserWithEmailAndPassword(auth, email, password)
268 |   }
269 | function loginWithGoogle(){
270 |     const result = signInWithPopup(auth, goog)
271 |     return result;
272 | }
273 |  
274 | async function loginWithGithub(){
275 |     const result = await signInWithPopup(auth, github)
276 |     return result;
277 | }
278 |   function login(email, password) {
279 |     return signInWithEmailAndPassword(auth, email, password)
280 |   }
281 |  
282 |   function logout() {
283 |     return signOut(auth)
284 |   }
285 |  
286 |   function resetPassword(email) {
287 |     return sendPasswordResetEmail(auth, email)
288 |   }
289 |  
290 |   function updateEmail(email) {
291 |     return currentUser.updateEmail(email)
292 |   }
293 |  
294 |   function updatePassword(password) {
295 |     return currentUser.updatePassword(password)
296 |   }
297 |  
298 |   useEffect(() => {
299 |     const unsubscribe = auth.onAuthStateChanged(user => {
300 |       setCurrentUser(user)
301 |       setLoading(false)
302 |     })
303 |  
304 |     return unsubscribe
305 |   }, [])
306 |  
307 |   const value = {
308 |     currentUser,
309 |     loginWithGoogle,
310 |     loginWithGithub,
311 |     login,
312 |     signup,
313 |     logout,
314 |     resetPassword,
315 |     updateEmail,
316 |     updatePassword
317 |   }
318 |  
319 |   return (
320 |     <AuthContext.Provider value={value}>
321 |       {!loading && children}
322 |     </AuthContext.Provider>
323 |   )
324 | }
325 | 326 |
327 |
328 | 333 | 334 | 339 | 340 | 341 | 342 | 343 | -------------------------------------------------------------------------------- /coverage/lcov-report/hyperion/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for hyperion/src 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files hyperion/src

23 |
24 | 25 |
26 | 5.45% 27 | Statements 28 | 3/55 29 |
30 | 31 | 32 |
33 | 0% 34 | Branches 35 | 0/4 36 |
37 | 38 | 39 |
40 | 0% 41 | Functions 42 | 0/16 43 |
44 | 45 | 46 |
47 | 5.45% 48 | Lines 49 | 3/55 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 |
FileStatementsBranchesFunctionsLines
AuthContext.js 84 |
85 |
13.04%3/230%0/20%0/1213.04%3/23
Login.js 99 |
100 |
0%0/320%0/20%0/40%0/32
113 |
114 |
115 |
116 | 121 | 122 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for All files 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files

23 |
24 | 25 |
26 | Unknown% 27 | Statements 28 | 0/0 29 |
30 | 31 | 32 |
33 | Unknown% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | Unknown% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | Unknown% 48 | Lines 49 | 0/0 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 |
FileStatementsBranchesFunctionsLines
83 |
84 |
85 |
86 | 91 | 92 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Hyperionn/0f56d421def6c3ffcf09bb946ebc69aa4a63b764/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /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.info: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Hyperionn/0f56d421def6c3ffcf09bb946ebc69aa4a63b764/coverage/lcov.info -------------------------------------------------------------------------------- /dist/bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | Copyright (c) 2018 Jed Watson. 3 | Licensed under the MIT License (MIT), see 4 | http://jedwatson.github.io/classnames 5 | */ 6 | 7 | /*! 8 | * @kurkle/color v0.2.1 9 | * https://github.com/kurkle/color#readme 10 | * (c) 2022 Jukka Kurkela 11 | * Released under the MIT License 12 | */ 13 | 14 | /*! 15 | * Chart.js v3.9.1 16 | * https://www.chartjs.org 17 | * (c) 2022 Chart.js Contributors 18 | * Released under the MIT License 19 | */ 20 | 21 | /*! 22 | * chartjs-plugin-streaming v2.0.0 23 | * https://nagix.github.io/chartjs-plugin-streaming 24 | * (c) 2017-2021 Akihiko Kusanagi 25 | * Released under the MIT license 26 | */ 27 | 28 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 29 | 30 | /** 31 | * @license 32 | * Copyright 2017 Google LLC 33 | * 34 | * Licensed under the Apache License, Version 2.0 (the "License"); 35 | * you may not use this file except in compliance with the License. 36 | * You may obtain a copy of the License at 37 | * 38 | * http://www.apache.org/licenses/LICENSE-2.0 39 | * 40 | * Unless required by applicable law or agreed to in writing, software 41 | * distributed under the License is distributed on an "AS IS" BASIS, 42 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 43 | * See the License for the specific language governing permissions and 44 | * limitations under the License. 45 | */ 46 | 47 | /** 48 | * @license 49 | * Copyright 2019 Google LLC 50 | * 51 | * Licensed under the Apache License, Version 2.0 (the "License"); 52 | * you may not use this file except in compliance with the License. 53 | * You may obtain a copy of the License at 54 | * 55 | * http://www.apache.org/licenses/LICENSE-2.0 56 | * 57 | * Unless required by applicable law or agreed to in writing, software 58 | * distributed under the License is distributed on an "AS IS" BASIS, 59 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 60 | * See the License for the specific language governing permissions and 61 | * limitations under the License. 62 | */ 63 | 64 | /** 65 | * @license 66 | * Copyright 2020 Google LLC 67 | * 68 | * Licensed under the Apache License, Version 2.0 (the "License"); 69 | * you may not use this file except in compliance with the License. 70 | * You may obtain a copy of the License at 71 | * 72 | * http://www.apache.org/licenses/LICENSE-2.0 73 | * 74 | * Unless required by applicable law or agreed to in writing, software 75 | * distributed under the License is distributed on an "AS IS" BASIS, 76 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 77 | * See the License for the specific language governing permissions and 78 | * limitations under the License. 79 | */ 80 | 81 | /** 82 | * @license 83 | * Copyright 2020 Google LLC. 84 | * 85 | * Licensed under the Apache License, Version 2.0 (the "License"); 86 | * you may not use this file except in compliance with the License. 87 | * You may obtain a copy of the License at 88 | * 89 | * http://www.apache.org/licenses/LICENSE-2.0 90 | * 91 | * Unless required by applicable law or agreed to in writing, software 92 | * distributed under the License is distributed on an "AS IS" BASIS, 93 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 94 | * See the License for the specific language governing permissions and 95 | * limitations under the License. 96 | */ 97 | 98 | /** 99 | * @license 100 | * Copyright 2021 Google LLC 101 | * 102 | * Licensed under the Apache License, Version 2.0 (the "License"); 103 | * you may not use this file except in compliance with the License. 104 | * You may obtain a copy of the License at 105 | * 106 | * http://www.apache.org/licenses/LICENSE-2.0 107 | * 108 | * Unless required by applicable law or agreed to in writing, software 109 | * distributed under the License is distributed on an "AS IS" BASIS, 110 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 111 | * See the License for the specific language governing permissions and 112 | * limitations under the License. 113 | */ 114 | 115 | /** 116 | * @license 117 | * Copyright 2022 Google LLC 118 | * 119 | * Licensed under the Apache License, Version 2.0 (the "License"); 120 | * you may not use this file except in compliance with the License. 121 | * You may obtain a copy of the License at 122 | * 123 | * http://www.apache.org/licenses/LICENSE-2.0 124 | * 125 | * Unless required by applicable law or agreed to in writing, software 126 | * distributed under the License is distributed on an "AS IS" BASIS, 127 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 128 | * See the License for the specific language governing permissions and 129 | * limitations under the License. 130 | */ 131 | 132 | /** 133 | * @license React 134 | * react-dom.production.min.js 135 | * 136 | * Copyright (c) Facebook, Inc. and its affiliates. 137 | * 138 | * This source code is licensed under the MIT license found in the 139 | * LICENSE file in the root directory of this source tree. 140 | */ 141 | 142 | /** 143 | * @license React 144 | * react-is.production.min.js 145 | * 146 | * Copyright (c) Facebook, Inc. and its affiliates. 147 | * 148 | * This source code is licensed under the MIT license found in the 149 | * LICENSE file in the root directory of this source tree. 150 | */ 151 | 152 | /** 153 | * @license React 154 | * react-jsx-runtime.production.min.js 155 | * 156 | * Copyright (c) Facebook, Inc. and its affiliates. 157 | * 158 | * This source code is licensed under the MIT license found in the 159 | * LICENSE file in the root directory of this source tree. 160 | */ 161 | 162 | /** 163 | * @license React 164 | * react.production.min.js 165 | * 166 | * Copyright (c) Facebook, Inc. and its affiliates. 167 | * 168 | * This source code is licensed under the MIT license found in the 169 | * LICENSE file in the root directory of this source tree. 170 | */ 171 | 172 | /** 173 | * @license React 174 | * scheduler.production.min.js 175 | * 176 | * Copyright (c) Facebook, Inc. and its affiliates. 177 | * 178 | * This source code is licensed under the MIT license found in the 179 | * LICENSE file in the root directory of this source tree. 180 | */ 181 | 182 | /** 183 | * React Router DOM v6.3.0 184 | * 185 | * Copyright (c) Remix Software Inc. 186 | * 187 | * This source code is licensed under the MIT license found in the 188 | * LICENSE.md file in the root directory of this source tree. 189 | * 190 | * @license MIT 191 | */ 192 | 193 | /** 194 | * React Router v6.3.0 195 | * 196 | * Copyright (c) Remix Software Inc. 197 | * 198 | * This source code is licensed under the MIT license found in the 199 | * LICENSE.md file in the root directory of this source tree. 200 | * 201 | * @license MIT 202 | */ 203 | 204 | /** @license MUI v5.8.7 205 | * 206 | * This source code is licensed under the MIT license found in the 207 | * LICENSE file in the root directory of this source tree. 208 | */ 209 | 210 | /** @license MUI v5.9.3 211 | * 212 | * This source code is licensed under the MIT license found in the 213 | * LICENSE file in the root directory of this source tree. 214 | */ 215 | 216 | /** @license React v16.13.1 217 | * react-is.production.min.js 218 | * 219 | * Copyright (c) Facebook, Inc. and its affiliates. 220 | * 221 | * This source code is licensed under the MIT license found in the 222 | * LICENSE file in the root directory of this source tree. 223 | */ 224 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | Hyperion
-------------------------------------------------------------------------------- /dist/src/assets/Hyperion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Hyperionn/0f56d421def6c3ffcf09bb946ebc69aa4a63b764/dist/src/assets/Hyperion.png -------------------------------------------------------------------------------- /firebase.js: -------------------------------------------------------------------------------- 1 | import { initializeApp } from "firebase/app"; 2 | import * as firebase from 'firebase/auth'; 3 | 4 | const firebaseConfig = { 5 | apiKey: "AIzaSyBp29QyjHWW6Jojs2Hufn7z8zmNMVw5290", 6 | authDomain: "hyperion-272ea.firebaseapp.com", 7 | projectId: "hyperion-272ea", 8 | storageBucket: "hyperion-272ea.appspot.com", 9 | messagingSenderId: "90144758527", 10 | appId: "1:90144758527:web:462b4eb5b5408c5e63caca", 11 | measurementId: "G-3KM8KR7HS2" 12 | }; 13 | 14 | // Initialize Firebase 15 | const app = initializeApp(firebaseConfig); 16 | export const auth = firebase.getAuth(app); 17 | export default app; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyperion", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "index.tsx", 6 | "scripts": { 7 | "start": "NODE_ENV=production node server/server.js", 8 | "build": "webpack", 9 | "dev": "NODE_ENV=development nodemon server/server.js & NODE_ENV=development webpack serve --open", 10 | "test": "vitest --environment jsdom" 11 | }, 12 | "author": "Team jakaL", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@babel/plugin-transform-typescript": "^7.18.12", 16 | "@babel/preset-react": "^7.18.6", 17 | "@emotion/styled": "^11.10.0", 18 | "@mui/icons-material": "^5.8.4", 19 | "@mui/material": "^5.9.3", 20 | "@mui/x-data-grid": "^5.15.3", 21 | "axios": "^0.27.2", 22 | "bootstrap": "^5.2.0", 23 | "chart": "^0.1.2", 24 | "chart.js": "^3.9.1", 25 | "chartjs-adapter-luxon": "^1.2.0", 26 | "chartjs-plugin-streaming": "^2.0.0", 27 | "express": "^4.18.1", 28 | "express-openid-connect": "^2.8.0", 29 | "firebase": "^9.9.2", 30 | "firebaseui": "^6.0.1", 31 | "luxon": "^3.0.1", 32 | "node-fetch": "^3.2.10", 33 | "nodemon": "^2.0.19", 34 | "passport": "^0.6.0", 35 | "pg": "^8.7.3", 36 | "postcss-loader": "^7.0.1", 37 | "react": "^18.2.0", 38 | "react-bootstrap": "^2.5.0", 39 | "react-chartjs-2": "^4.3.1", 40 | "react-dom": "^18.2.0", 41 | "react-router": "^6.3.0", 42 | "react-router-dom": "^6.3.0", 43 | "sass": "^1.54.0", 44 | "sass-loader": "^13.0.2", 45 | "socket-io": "^1.0.0", 46 | "socket.io": "^4.5.1", 47 | "socket.io-client": "^4.5.1" 48 | }, 49 | "devDependencies": { 50 | "@babel/core": "^7.18.10", 51 | "@babel/plugin-syntax-jsx": "^7.18.6", 52 | "@babel/plugin-transform-modules-commonjs": "^7.18.6", 53 | "@babel/plugin-transform-runtime": "^7.18.10", 54 | "@babel/preset-env": "^7.18.10", 55 | "@babel/preset-typescript": "^7.18.6", 56 | "@testing-library/jest-dom": "^5.16.5", 57 | "@testing-library/react": "^13.3.0", 58 | "@testing-library/user-event": "^14.4.3", 59 | "@typescript-eslint/eslint-plugin": "^5.35.1", 60 | "@typescript-eslint/parser": "^5.35.1", 61 | "@vitejs/plugin-react": "^2.0.1", 62 | "babel-core": "^6.26.3", 63 | "babel-loader": "^8.2.5", 64 | "babel-plugin-transform-class-properties": "^6.24.1", 65 | "babel-preset-es2015": "^6.24.1", 66 | "babel-preset-react": "^6.24.1", 67 | "css-loader": "^6.7.1", 68 | "eslint": "^8.22.0", 69 | "eslint-plugin-react": "^7.30.1", 70 | "file-loader": "^6.2.0", 71 | "html-webpack-plugin": "^5.5.0", 72 | "identity-obj-proxy": "^3.0.0", 73 | "jsdom": "^20.0.0", 74 | "puppeteer": "^16.1.1", 75 | "react-test-renderer": "^18.2.0", 76 | "react-testing-library": "^8.0.1", 77 | "regenerator-runtime": "^0.13.9", 78 | "style-loader": "^3.3.1", 79 | "ts-loader": "^9.3.1", 80 | "ts-node": "^10.9.1", 81 | "typescript": "^4.7.4", 82 | "url-loader": "^4.1.1", 83 | "vitest": "^0.22.1", 84 | "webpack": "^5.74.0", 85 | "webpack-cli": "^4.10.0", 86 | "webpack-dev-server": "^4.9.3" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /server/Controllers/metricController.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const axios = require('axios'); 3 | const pg = require('../models/errorLog'); 4 | 5 | 6 | const prometheusServerHostname = 'http://localhost:'; 7 | const prometheusPort = '9090'; 8 | const url = prometheusServerHostname + prometheusPort; 9 | 10 | 11 | const queryStringDictionary = { 12 | underReplicated: '/api/v1/query?query=kafka_server_replicamanager_underreplicatedpartitions', 13 | offlinePartitions: '/api/v1/query?query=kafka_controller_kafkacontroller_offlinepartitionscount', 14 | activeControllers: '/api/v1/query?query=kafka_controller_kafkacontroller_activecontrollercount', 15 | responseRate: '/api/v1/query?query=kafka_connect_connect_metrics_response_rate', 16 | requestRate: '/api/v1/query?query=kafka_connect_connect_metrics_request_rate', 17 | avgReqLatency: '/api/v1/query?query=kafka_producer_producer_metrics_request_latency_avg', 18 | avgReqLatencyZookeepers: '/api/v1/query?query=zookeeper_avgrequestlatency', 19 | }; 20 | const metricController = {}; 21 | 22 | 23 | metricController.getMetricData = async (req, res, next) => { 24 | const allMetrics = req.body; 25 | io.on('connection', (socket) => { 26 | console.log(socket.id, 'connected inside getMetricData'); 27 | }) 28 | const queryString = queryStringDictionary[metric]; 29 | try { 30 | const data = await axios.get(`${url}${queryString}`); 31 | res.locals.metricData = data.data.data.result; 32 | return next(); 33 | } catch (error) { 34 | return next({ 35 | log: "Error in getData middleware: " + error, 36 | status: 404, 37 | message: "Error occurred when obtaining Prometheus data: " + error 38 | }) 39 | } 40 | } 41 | 42 | 43 | metricController.getErrors = async (req, res, next) => { 44 | const {email} = req.body; 45 | const queryString = `SELECT * FROM errors WHERE email = '${email}' LIMIT 100`; 46 | const result = await pg.query(queryString); 47 | res.locals.errorData = result.rows; 48 | return next(); 49 | } 50 | 51 | module.exports = metricController; 52 | 53 | -------------------------------------------------------------------------------- /server/Controllers/userController.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const axios = require('axios'); 3 | const pg = require('../models/users'); 4 | const { path } = require('../server'); 5 | 6 | 7 | const userController = {}; 8 | 9 | userController.connectPort = async (req, res, next) => { 10 | const { port, domain } = req.body; 11 | const queryParameter = [ domain, port, 1 ]; 12 | const queryString = 'UPDATE users SET domain = $1, port = $2 WHERE user_id = $3'; 13 | try { 14 | const result = await pg.query(queryString, queryParameter); 15 | res.locals.domain = domain; 16 | res.locals.port = port; 17 | return next(); 18 | } catch (err) { 19 | return next(err) 20 | } 21 | } 22 | 23 | userController.checkUser = async (req, res, next) => { 24 | const {email} = req.body; 25 | const queryParameter = email; 26 | return next(); 27 | } 28 | 29 | module.exports = userController; -------------------------------------------------------------------------------- /server/models/errorLog.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | const PG_URI = 'postgres://dcdctlxp:Vjjyr-AwlQwjVcU0XHtIaPHnbKU2HiLh@queenie.db.elephantsql.com/dcdctlxp' 3 | 4 | const pool = new Pool({ 5 | connectionString: PG_URI, 6 | }); 7 | 8 | module.exports = { 9 | query: (text, params, callback) => { 10 | console.log('QUERY: ', text); 11 | return pool.query(text, params, callback); 12 | }, 13 | }; -------------------------------------------------------------------------------- /server/models/users.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | const PG_URI = 'postgres://dcdctlxp:Vjjyr-AwlQwjVcU0XHtIaPHnbKU2HiLh@queenie.db.elephantsql.com/dcdctlxp' 3 | 4 | const pool = new Pool({ 5 | connectionString: PG_URI, 6 | }); 7 | 8 | module.exports = { 9 | query: (text, params, callback) => { 10 | console.log('QUERY: ', text); 11 | return pool.query(text, params, callback); 12 | }, 13 | }; -------------------------------------------------------------------------------- /server/routers/Router.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const metricController = require('../Controllers/metricController'); 4 | const userController = require('../Controllers/userController'); 5 | const axios = require('axios') 6 | const prometheusServerHostname = 'http://localhost:'; 7 | const prometheusPort = '9090'; 8 | 9 | 10 | 11 | const queryStringDictionary = { 12 | underReplicated: '/api/v1/query?query=kafka_server_replicamanager_underreplicatedpartitions', 13 | offlinePartitions: '/api/v1/query?query=kafka_controller_kafkacontroller_offlinepartitionscount', 14 | activeControllers: '/api/v1/query?query=kafka_controller_kafkacontroller_activecontrollercount', 15 | responseRate: '/api/v1/query?query=kafka_connect_connect_metrics_response_rate', 16 | requestRate: '/api/v1/query?query=kafka_connect_connect_metrics_request_rate', 17 | avgReqLatency: '/api/v1/query?query=kafka_producer_producer_metrics_request_latency_avg', 18 | avgReqLatencyZookeepers: '/api/v1/query?query=zookeeper_avgrequestlatency', 19 | }; 20 | 21 | 22 | router.post('/errors', 23 | metricController.getErrors, 24 | (req, res) => { 25 | return res.status(200).json(res.locals.errorData); 26 | } 27 | ) 28 | 29 | 30 | router.post('/ports', 31 | userController.connectPort, 32 | (req, res) => { 33 | return res.status(200).send('Saved prometheus information'); 34 | } 35 | ) 36 | 37 | module.exports = router; -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const { urlencoded } = require('express'); 2 | const express = require('express'); 3 | const path = require('path'); 4 | const app = express(); 5 | const router = require('./routers/Router'); 6 | const PORT = 3001; 7 | const axios = require('axios'); 8 | const pg = require('./models/errorLog'); 9 | 10 | const queryStringDictionary = { 11 | underReplicated: '/api/v1/query?query=kafka_server_replicamanager_underreplicatedpartitions', 12 | offlinePartitions: '/api/v1/query?query=kafka_controller_kafkacontroller_offlinepartitionscount', 13 | activeControllers: '/api/v1/query?query=kafka_controller_kafkacontroller_activecontrollercount', 14 | responseRate: '/api/v1/query?query=kafka_connect_connect_metrics_response_rate', 15 | requestRate: '/api/v1/query?query=kafka_connect_connect_metrics_request_rate', 16 | avgReqLatency: '/api/v1/query?query=kafka_producer_producer_metrics_request_latency_avg', 17 | avgReqLatencyZookeepers: '/api/v1/query?query=zookeeper_avgrequestlatency', 18 | producerByteRate: '/api/v1/query?query=kafka_producer_producer_metrics_outgoing_byte_rate', 19 | bytesConsumedRate: '/api/v1/query?query=kafka_consumer_consumer_fetch_manager_metrics_bytes_consumed_rate' 20 | }; 21 | 22 | const parseData = (data, metric, email) => { 23 | console.log('parse data email: ', email) 24 | const queryString = `INSERT INTO errors 25 | (name, instance, env, value, time, email) 26 | VALUES ($1, $2, $3, $4, $5, $6)`; 27 | const dataArray = data.data.data.result; 28 | if(metric === 'underReplicated' || metric === 'offlinePartitions'){ 29 | try { 30 | dataArray.forEach((metricObj, ind, arr) => { 31 | if (Number(metricObj.value[1]) > 0) { 32 | const name = dataArray[ind].metric.__name__; 33 | const instance = dataArray[ind].metric.instance; 34 | const env = dataArray[ind].metric.env; 35 | const value = Number(dataArray[ind].value[1]); 36 | const today = new Date(); 37 | const date = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate(); 38 | const partTime = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds(); 39 | const time = date+' '+partTime; 40 | const queryParameter = [name, instance, env, value, time, email]; 41 | pg.query(queryString, queryParameter) 42 | .then((result) => { 43 | console.log('inserted out of range metric into db: ', result); 44 | return dataArray; 45 | }) 46 | .catch(err => {throw new Error('ERROR LOGGING OUT OF RANGE METRIC OFFLINE AND UNDER')}); 47 | } 48 | }); 49 | return dataArray; 50 | } catch(error) { 51 | console.log('Error in parseData underReplicated and offlinePartitions: ', error); 52 | throw new Error('Error setting data metrics') 53 | } 54 | } else if (metric === 'activeControllers') { 55 | try { 56 | const sum = dataArray.reduce((acc, curr) => 57 | Number(acc) + Number(curr.value[1]) 58 | , 0); 59 | if (sum !== 1) { 60 | const name = dataArray[0].metric.__name__; 61 | const instance = dataArray[0].metric.instance; 62 | const env = dataArray[0].metric.env; 63 | const value = sum; 64 | const today = new Date(); 65 | const date = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate(); 66 | const partTime = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds(); 67 | const time = date+' '+partTime; 68 | const queryParameter = [name, instance, env, value, time, email]; 69 | pg.query(queryString, queryParameter) 70 | .then((result) => { 71 | console.log('inserted out of range metric into db: ', result); 72 | return dataArray; 73 | }) 74 | .catch(err =>{ throw new Error('ERROR LOGGING OUT OF RANGE METRIC ACTIVE')}); 75 | } 76 | return dataArray; 77 | } catch(error) { 78 | console.log('Error in parseData activeControllers: ', error); 79 | throw new Error('Error setting data metrics') 80 | } 81 | 82 | } else { 83 | try{ 84 | const tempMetricData = dataArray; 85 | 86 | const arrayWithDataForAnish = [] 87 | tempMetricData.forEach(dataObj => { 88 | arrayWithDataForAnish.push({x: Date.now(), y: Number(dataObj.value[1]), instance: dataObj.metric.instance}); 89 | }); 90 | return arrayWithDataForAnish; 91 | }catch(error){ 92 | console.log('error in setting metric data: ', error) 93 | throw new Error('Error setting data metrics') 94 | } 95 | 96 | } 97 | } 98 | const allMetrics = [ 'underReplicated', 'offlinePartitions', 'activeControllers', 'avgReqLatency', 'responseRate', 'requestRate', 'producerByteRate', 'bytesConsumedRate']; 99 | 100 | const getDataAndEmit = (url, email) => { 101 | allMetrics.forEach( async (metric) => { 102 | try{ 103 | const queryString = queryStringDictionary[metric] 104 | const data = await axios.get(`${url}${queryString}`); 105 | const parsedData = parseData(data, metric, email) 106 | console.log('back end sending this data through socket: ', metric, parsedData) 107 | io.emit(metric, parsedData); 108 | }catch(error){ 109 | console.log('error in get data and emit', error) 110 | throw new Error('Error in get Data and Emit') 111 | } 112 | }) 113 | } 114 | 115 | 116 | const http = require('http').createServer(); 117 | 118 | const io = require('socket.io')(http, { 119 | cors: { origin: "*" } 120 | }); 121 | global.io = io; 122 | 123 | 124 | io.on('connection', socket => { 125 | 126 | socket.on("ip&email", (ip, email) => { 127 | setInterval(getDataAndEmit, 5000, ip, email); //ip = domain:port 128 | }) 129 | 130 | 131 | }) 132 | 133 | //socket hosted on 3500 134 | http.listen(3500, () => console.log('listening on http://localhost:3500') ); 135 | 136 | app.use(express.urlencoded({ extended: true })); 137 | app.use(express.json()); 138 | 139 | app.use('/', express.static(path.resolve(__dirname,'../dist'))); 140 | 141 | //Serve index.html file from src 142 | app.get('/', (req, res) => { 143 | return res.sendFile(path.resolve(__dirname, '../src/index.html')); 144 | }); 145 | 146 | app.use('/server', router) 147 | 148 | 149 | /** 150 | * configure express global error handler 151 | * @see https://expressjs.com/en/guide/error-handling.html#writing-error-handlers 152 | */ 153 | app.use((err, req, res, next) => { 154 | 155 | const defaultErr = { 156 | log: 'Express error handler caught unknown middleware error', 157 | status: 500, 158 | message: { err: 'An error occurred: ' + err }, 159 | }; 160 | 161 | const errorObj = Object.assign({}, defaultErr, err); 162 | console.log(errorObj.log); 163 | console.log(errorObj.message); 164 | 165 | return res.status(errorObj.status).json(errorObj.message) 166 | }); 167 | 168 | app.listen(PORT, () => console.log('LISTENING ON PORT: ' + PORT)); 169 | 170 | module.exports = app; -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import './app.scss'; 2 | import React from "react" 3 | import { Container } from "@mui/material" 4 | import { AuthProvider } from "./Pages/AuthContext" 5 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; 6 | import MainDisplay from './Pages/MainDisplay'; 7 | import Login from './Pages/Login'; 8 | import Signup from "./Pages/Signup"; 9 | import PrivateRoute from "./Pages/PrivateRoute"; 10 | import ForgotPassword from "./Pages/ForgotPassword"; 11 | import ErrorLogDisplay from './Pages/ErrorLogDisplay'; 12 | 13 | import { createTheme, ThemeProvider } from "@mui/material/styles"; 14 | import { io } from "socket.io-client"; 15 | 16 | const socket = io('ws://localhost:3500'); 17 | 18 | const theme = createTheme({ 19 | palette: { 20 | primary: { 21 | main: "#63489b", 22 | light: "#d8d8d8", 23 | dark: "#120a27", 24 | }, 25 | secondary: { 26 | main: "#f39566", 27 | light: "#f3be66", 28 | dark: "#ce10fa", 29 | }, 30 | }, 31 | }); 32 | 33 | 34 | const App: React.FC = () => { 35 | return ( 36 | 37 | 38 | 39 | 40 | }/> 41 | }/> 42 | } /> 43 | } /> 44 | } /> 45 | 46 | 47 | 48 | 49 | ) 50 | } 51 | 52 | export default App; 53 | -------------------------------------------------------------------------------- /src/Components/ActiveControllers.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect, MyProvider } from 'react' 2 | import { Paper, Box,Typography, Card, Popover } from '@mui/material'; 3 | import ReadMoreIcon from '@mui/icons-material/ReadMore'; 4 | 5 | const ActiveControllers = ({ activeControllers }) => { 6 | const [kafkaData, setKafkaData] = useState({ 7 | activeControllersParsed : [], 8 | }) 9 | let previousValues = useRef({ activeControllers }) 10 | 11 | useEffect(() => { 12 | if (previousValues.current.activeControllers !== activeControllers) { 13 | parseSimpleKeyMetrics( activeControllers); 14 | previousValues.current = {activeControllers} 15 | console.log('activeControllers: ', activeControllers); 16 | } 17 | }); 18 | 19 | function parseSimpleKeyMetrics( actCont ) { 20 | 21 | let activeContSum = 0; 22 | if ( JSON.stringify(actCont) === '{}') return; 23 | 24 | //loop through data and sum all values 25 | for (const metricChunk of actCont) { 26 | activeContSum += Number(metricChunk.value[1]); 27 | } 28 | 29 | //set state of kafkaData 30 | setKafkaData({ activeControllersParsed: activeContSum }); 31 | } 32 | 33 | 34 | const [anchorEl, setAnchorEl] = useState(null); 35 | 36 | const handleClick = (event) => { 37 | setAnchorEl(event.currentTarget); 38 | }; 39 | 40 | const handleClose = () => { 41 | setAnchorEl(null); 42 | }; 43 | 44 | const open = Boolean(anchorEl); 45 | 46 | const id = (open) ? 'simple-popover' : undefined; 47 | 48 | const { activeControllersParsed } = kafkaData; 49 | 50 | return ( 51 | 52 | 53 | Active Controllers 54 | 55 | {activeControllersParsed} 56 | 65 | 75 | The first node to boot in a Kafka cluster automatically becomes the controller, and there can be only one. The controller in a Kafka cluster is responsible for maintaining the list of partition leaders, and coordinating leadership transitions (in the event a partition leader becomes unavailable). If it becomes necessary to replace the controller, ZooKeeper chooses a new controller randomly from the pool of brokers. The sum of ActiveControllerCount across all of your brokers should always equal one, and you should alert on any other value that lasts for longer than one second. 76 | Source: https://www.datadoghq.com/blog/monitoring-kafka-performance-metrics/ 77 | 78 | 79 | 80 | 81 | ) 82 | } 83 | export default ActiveControllers; -------------------------------------------------------------------------------- /src/Components/AvgRequestLatency.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react' 2 | import { Paper, Box, Typography, Container, Grid, Popover, Button } from '@mui/material'; 3 | import ReadMoreIcon from '@mui/icons-material/ReadMore'; 4 | import Chart from 'chart.js/auto'; 5 | import { Line} from "react-chartjs-2"; 6 | import 'chartjs-adapter-luxon'; 7 | import StreamingPlugin from 'chartjs-plugin-streaming'; 8 | 9 | 10 | Chart.register(StreamingPlugin); 11 | 12 | 13 | const AvgRequestLatency = ({avgReqLatency}) => { 14 | 15 | const [avgDataSets, setDataSets] = useState([]); 16 | const [dataPoints, setDataPoints] = useState([]); 17 | 18 | useEffect(()=> { 19 | if (!avgDataSets.length) { 20 | makeDataSets(avgReqLatency); 21 | } 22 | makeDataPoints(avgReqLatency) 23 | console.log('sets: ', avgDataSets) 24 | console.log('props avgreq: ', avgReqLatency) 25 | }, [avgReqLatency]) 26 | 27 | const makeDataSets = incomingDataArray => { 28 | const colorArray = ['#f3be66', '#f39566', '#f366dc', '#ce10fa', '#63489b']; 29 | const output = []; 30 | for (let i = 0; i < incomingDataArray.length; i++){ 31 | let colorVal = Math.floor(Math.random() * 255) 32 | const obj = { 33 | label: incomingDataArray[i].instance, 34 | backgroundColor: `#f39566`, 35 | borderColor: `#f39566`, 36 | fill: false, 37 | data: [], 38 | } 39 | output.push(obj); 40 | } 41 | setDataSets(output); 42 | } 43 | 44 | function makeDataPoints(avgReqLat) { 45 | const newDataPoints = []; 46 | for (let i = 0; i < avgReqLat.length; i++) { 47 | newDataPoints.push({x: avgReqLat[i].x, y: avgReqLat[i].y}); 48 | } 49 | setDataPoints(newDataPoints); 50 | } 51 | 52 | const [anchorEl, setAnchorEl] = useState(null); 53 | 54 | const handleClick = (event) => { 55 | setAnchorEl(event.currentTarget); 56 | }; 57 | 58 | const handleClose = () => { 59 | setAnchorEl(null); 60 | }; 61 | 62 | const open = Boolean(anchorEl); 63 | const id = open ? 'simple-popover' : undefined; 64 | 65 | 66 | 67 | return ( 68 | 69 | Average Request Latency 70 | { 93 | chart.data.datasets.forEach((instance, index, array) => { 94 | instance.data.push({ 95 | x: dataPoints[index].x, 96 | y: dataPoints[index].y 97 | }); 98 | }); 99 | } 100 | } 101 | }, 102 | y:{ 103 | title : { 104 | display : true, 105 | text : 'ms' 106 | } 107 | } 108 | } 109 | }} 110 | /> 111 | 120 | 121 | 130 | The average request latency is a measure of the amount of time between when KafkaProducer.send() was called until the producer receives a response from the broker. 131 | Source: https://www.datadoghq.com/blog/monitoring-kafka-performance-metrics/ 132 | 133 | 134 | ) 135 | } 136 | export default AvgRequestLatency; 137 | -------------------------------------------------------------------------------- /src/Components/BytesConsumedRate.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react' 2 | import { Paper, Box, Typography, Container, Grid, Popover, Button } from '@mui/material'; 3 | import ReadMoreIcon from '@mui/icons-material/ReadMore'; 4 | import Chart from 'chart.js/auto'; 5 | import { Line} from "react-chartjs-2"; 6 | import 'chartjs-adapter-luxon'; 7 | import StreamingPlugin from 'chartjs-plugin-streaming'; 8 | 9 | 10 | Chart.register(StreamingPlugin); 11 | 12 | 13 | const BytesConsumedRate = ({bytesConsumedRate}) => { 14 | 15 | const [byteDataSets, setDataSets] = useState([]); 16 | const [dataPoints, setDataPoints] = useState([]); 17 | const [anchorEl, setAnchorEl] = useState(null); 18 | 19 | useEffect(()=> { 20 | if (!byteDataSets.length) { 21 | makeDataSets(bytesConsumedRate); 22 | } 23 | makeDataPoints(bytesConsumedRate) 24 | console.log('sets: ', byteDataSets) 25 | console.log('props byterate: ', bytesConsumedRate) 26 | }, [bytesConsumedRate]) 27 | 28 | 29 | const makeDataSets = incomingDataArray => { 30 | 31 | 32 | const colorArray = ['#f3be66', '#f6f6f6', '#ececec', '#2d2d2d', '#f3be66', '#f39566', '#f366dc', '#ce10fa', '#63489b', '#120a27']; 33 | const output = []; 34 | for (let i = 0; i < incomingDataArray.length; i++){ 35 | let colorVal = Math.floor(Math.random() * 255) 36 | const obj = { 37 | label: incomingDataArray[i].instance, 38 | backgroundColor: `${colorArray[i]}`, 39 | borderColor: `${colorArray[i]}`, 40 | fill: false, 41 | data: [], 42 | } 43 | output.push(obj); 44 | } 45 | setDataSets(output); 46 | } 47 | 48 | 49 | function makeDataPoints(byteData) { 50 | const newDataPoints = []; 51 | for (let i = 0; i < byteData.length; i++) { 52 | newDataPoints.push({x: byteData[i].x, y: byteData[i].y}); 53 | } 54 | setDataPoints(newDataPoints); 55 | } 56 | 57 | const handleClick = (event) => { 58 | setAnchorEl(event.currentTarget); 59 | }; 60 | 61 | const handleClose = () => { 62 | setAnchorEl(null); 63 | }; 64 | 65 | const open = Boolean(anchorEl); 66 | const id = open ? 'simple-popover' : undefined; 67 | 68 | 69 | 70 | return ( 71 | 72 | Bytes Consumed Rate 73 | { 96 | chart.data.datasets.forEach((instance, index, array) => { 97 | instance.data.push({ 98 | x: dataPoints[index].x, 99 | y: dataPoints[index].y 100 | }); 101 | }); 102 | } 103 | } 104 | }, 105 | y:{ 106 | title : { 107 | display : true, 108 | text : 'ms' 109 | } 110 | } 111 | } 112 | }} 113 | /> 114 | 123 | 133 | As with Kafka brokers, you will want to monitor your producer network throughput. Observing traffic volume over time is essential for determining whether you need to make changes to your network infrastructure. Monitoring producer network traffic will help to inform decisions on infrastructure changes, as well as to provide a window into the production rate of producers and identify sources of excessive traffic. 134 | Source: https://www.datadoghq.com/blog/monitoring-kafka-performance-metrics/ 135 | 136 | 137 | ) 138 | } 139 | export default BytesConsumedRate; -------------------------------------------------------------------------------- /src/Components/DataContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Grid, Box, Paper, TextField, Autocomplete, Container, Card, CardContent, CardActionArea } from '@mui/material'; 3 | import AvgRequestLatency from './AvgRequestLatency'; 4 | import ResponseRate from './ResponseRate'; 5 | import RequestRate from './RequestRate'; 6 | import UnderReplicated from './UnderReplicated'; 7 | import OfflinePartitions from './OfflinePartitions'; 8 | import ActiveControllers from './ActiveControllers'; 9 | import OutgoingByteRate from './OutgoingByteRate'; 10 | import BytesConsumedRate from './BytesConsumedRate'; 11 | 12 | 13 | const DataContainer = (props) => { 14 | 15 | function setFetchState() { 16 | setFetched(true); 17 | } 18 | 19 | return( 20 | <> 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | ) 85 | } 86 | 87 | export default DataContainer; 88 | -------------------------------------------------------------------------------- /src/Components/ErrorLogContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from 'react'; 2 | import { DataGrid } from '@mui/x-data-grid'; 3 | import Table from '@mui/material/Table'; 4 | import TableBody from '@mui/material/TableBody'; 5 | import TableCell from '@mui/material/TableCell'; 6 | import TableContainer from '@mui/material/TableContainer'; 7 | import TableHead from '@mui/material/TableHead'; 8 | import TableRow from '@mui/material/TableRow'; 9 | import Paper from '@mui/material/Paper'; 10 | import Box from '@mui/material/Box'; 11 | import axios from 'axios'; 12 | 13 | const columns = [ 14 | { field: 'error_id', headerName: 'Error ID' }, 15 | { field: 'instance', headerName: 'Instance' }, 16 | { field: 'env', headerName: 'Environment' }, 17 | { 18 | field: 'value', 19 | headerName: 'Value', 20 | type: 'number' 21 | }, 22 | { field: 'name', headerName: 'Name' }, 23 | { field: 'time', headerName: 'Time' } 24 | ]; 25 | 26 | const rows = []; 27 | 28 | export default function ErrorLogContainer() { 29 | 30 | const [errorData, setErrorData] = useState( 31 | [ 32 | { 33 | error_id: '', 34 | instance: '', 35 | env: '', 36 | value: '', 37 | name: '', 38 | time: '', 39 | email: '' 40 | } 41 | ] 42 | ); 43 | 44 | useEffect(()=> { 45 | getErrorData(); 46 | console.log('state errorData in useEffect: ', errorData) 47 | }, []) 48 | 49 | 50 | const getErrorData = async () => { 51 | 52 | const email = localStorage.getItem('email'); 53 | console.log('email in geterror fetch request front end: ', email); 54 | const result = await axios.post('/server/errors', { email }) 55 | console.log('get request result.data', result.data); 56 | setErrorData(result.data); 57 | console.log('updated errorData in get error data', errorData) 58 | } 59 | 60 | 61 | return ( 62 | 63 | 64 | 65 | 66 | Error ID 67 | Instance 68 | Environment 69 | Value 70 | Name 71 | Time 72 | 73 | 74 | {errorData.map((row) => ( 75 | 76 | {row.error_id} 77 | {row.instance} 78 | {row.env} 79 | {row.value} 80 | {row.name} 81 | {row.time} 82 | ))} 83 | 84 |
85 |
86 | ); 87 | } 88 | -------------------------------------------------------------------------------- /src/Components/NavBar.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from "react"; 2 | import { 3 | AppBar, 4 | Box, 5 | Button, 6 | Modal, 7 | Typography, 8 | Menu, 9 | MenuItem, 10 | Fade 11 | } from "@mui/material"; 12 | import { useAuth } from "../Pages/AuthContext"; 13 | import { useNavigate } from "react-router-dom" 14 | import LogoutIcon from '@mui/icons-material/Logout'; 15 | 16 | 17 | 18 | const NavBar = (): JSX.Element => { 19 | 20 | interface errorMsg { result: string } 21 | 22 | const [error, setError] = useState ({ result: '' }) 23 | const navigate = useNavigate() 24 | const { logout } = useAuth() 25 | 26 | const button: { backgroundColor: string, color: string, margin: string, boxShadow: string, '&:hover': {backgroundColor: string, color: string }} = { 27 | backgroundColor: "#ffffff", 28 | color: '#a4a4a4', 29 | margin:"10px", 30 | boxShadow:"none", 31 | "&:hover": { 32 | backgroundColor: "#f6f6f6", 33 | color: "#a4a4a4" 34 | }, 35 | } 36 | 37 | async function handleSubmit(e: React.MouseEvent) { 38 | e.preventDefault() 39 | try { 40 | await logout() 41 | navigate("/") 42 | } catch { 43 | setError({ result: "Failed to logout" }) 44 | } 45 | } 46 | 47 | 48 | return( 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | ); 59 | }; 60 | 61 | export default NavBar; -------------------------------------------------------------------------------- /src/Components/OfflinePartitions.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect, MyProvider } from 'react' 2 | import { Paper, Box,Typography, Card, Popover } from '@mui/material'; 3 | import ReadMoreIcon from '@mui/icons-material/ReadMore'; 4 | 5 | const OfflinePartitions = ({ offlinePartitions }) => { 6 | const [kafkaData, setKafkaData] = useState({ 7 | offlinePartitionsParsed : [] 8 | }) 9 | let previousValues = useRef({offlinePartitions}) 10 | 11 | useEffect(() => { 12 | if (previousValues.current.offlinePartitions !== offlinePartitions) { 13 | parseSimpleKeyMetrics(offlinePartitions); 14 | previousValues.current = { offlinePartitions } 15 | console.log('offlinePartitions: ', offlinePartitions); 16 | } 17 | }); 18 | 19 | function parseSimpleKeyMetrics(offPart) { 20 | let offlinePartSum = 0; 21 | if (JSON.stringify(offPart) === '{}') return; 22 | 23 | for (const metricChunk of offPart) { 24 | offlinePartSum += Number(metricChunk.value[1]); 25 | } 26 | 27 | setKafkaData({ offlinePartitionsParsed: offlinePartSum }); 28 | } 29 | 30 | const [anchorEl, setAnchorEl] = useState(null); 31 | 32 | const handleClick = (event) => { 33 | setAnchorEl(event.currentTarget); 34 | }; 35 | 36 | const handleClose = () => { 37 | setAnchorEl(null); 38 | }; 39 | 40 | const open = Boolean(anchorEl); 41 | 42 | const id = (open) ? 'simple-popover' : undefined; 43 | 44 | const { offlinePartitionsParsed } = kafkaData; 45 | 46 | return ( 47 | 48 | 49 | Offline Partitions 50 | 51 | {offlinePartitionsParsed} 52 | 61 | 71 | This metric reports the number of partitions without an active leader. Because all read and write operations are only performed on partition leaders, you should alert on a non-zero value for this metric to prevent service interruptions. Any partition without an active leader will be completely inaccessible, and both consumers and producers of that partition will be blocked until a leader becomes available. 72 | Source: https://www.datadoghq.com/blog/monitoring-kafka-performance-metrics/ 73 | 74 | 75 | 76 | 77 | ) 78 | } 79 | export default OfflinePartitions; -------------------------------------------------------------------------------- /src/Components/OutgoingByteRate.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react' 2 | import { Paper, Box, Typography, Container, Grid, Popover, Button } from '@mui/material'; 3 | import ReadMoreIcon from '@mui/icons-material/ReadMore'; 4 | import Chart from 'chart.js/auto'; 5 | import { Line} from "react-chartjs-2"; 6 | import 'chartjs-adapter-luxon'; 7 | import StreamingPlugin from 'chartjs-plugin-streaming'; 8 | 9 | 10 | Chart.register(StreamingPlugin); 11 | 12 | 13 | const OutgoingByteRate = ({producerByteRate}) => { 14 | 15 | const [byteDataSets, setDataSets] = useState([]); 16 | const [dataPoints, setDataPoints] = useState([]); 17 | const [anchorEl, setAnchorEl] = useState(null); 18 | 19 | useEffect(()=> { 20 | if (!byteDataSets.length) { 21 | makeDataSets(producerByteRate); 22 | } 23 | makeDataPoints(producerByteRate) 24 | console.log('sets: ', byteDataSets) 25 | console.log('props byterate: ', producerByteRate) 26 | }, [producerByteRate]) 27 | 28 | const makeDataSets = incomingDataArray => { 29 | const colorArray = ['#f3be66', '#f39566', '#f366dc', '#ce10fa', '#63489b']; 30 | const output = []; 31 | for (let i = 0; i < incomingDataArray.length; i++){ 32 | let colorVal = Math.floor(Math.random() * 255) 33 | const obj = { 34 | label: incomingDataArray[i].instance, 35 | backgroundColor: `#f39566`, 36 | borderColor: `#f39566`, 37 | fill: false, 38 | data: [], 39 | } 40 | output.push(obj); 41 | } 42 | setDataSets(output); 43 | } 44 | 45 | function makeDataPoints(byteData) { 46 | const newDataPoints = []; 47 | for (let i = 0; i < byteData.length; i++) { 48 | newDataPoints.push({x: byteData[i].x, y: byteData[i].y}); 49 | } 50 | setDataPoints(newDataPoints); 51 | } 52 | 53 | const handleClick = (event) => { 54 | setAnchorEl(event.currentTarget); 55 | }; 56 | 57 | const handleClose = () => { 58 | setAnchorEl(null); 59 | }; 60 | 61 | const open = Boolean(anchorEl); 62 | const id = open ? 'simple-popover' : undefined; 63 | 64 | 65 | 66 | return ( 67 | 68 | Producer Outgoing Byte Rate 69 | { 92 | chart.data.datasets.forEach((instance, index, array) => { 93 | instance.data.push({ 94 | x: dataPoints[index].x, 95 | y: dataPoints[index].y 96 | }); 97 | }); 98 | } 99 | } 100 | }, 101 | y:{ 102 | title : { 103 | display : true, 104 | text : 'ms' 105 | } 106 | } 107 | } 108 | }} 109 | /> 110 | 119 | 129 | As with Kafka brokers, you will want to monitor your producer network throughput. Observing traffic volume over time is essential for determining whether you need to make changes to your network infrastructure. Monitoring producer network traffic will help to inform decisions on infrastructure changes, as well as to provide a window into the production rate of producers and identify sources of excessive traffic. 130 | Source: https://www.datadoghq.com/blog/monitoring-kafka-performance-metrics/ 131 | 132 | 133 | ) 134 | } 135 | export default OutgoingByteRate; -------------------------------------------------------------------------------- /src/Components/RequestRate.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react' 2 | import { Paper, Box, Typography, Container, Grid, Popover, Button } from '@mui/material'; 3 | import ReadMoreIcon from '@mui/icons-material/ReadMore'; 4 | import Chart from 'chart.js/auto'; 5 | import { Line} from "react-chartjs-2"; 6 | import 'chartjs-adapter-luxon'; 7 | import StreamingPlugin from 'chartjs-plugin-streaming'; 8 | 9 | Chart.register(StreamingPlugin); 10 | 11 | 12 | const pollingInterval = 5000; 13 | 14 | const RequestRate = ({requestRate}) => { 15 | 16 | const [reqRateSets, setReqRateSets] = useState([]); 17 | const [dataPoints, setDataPoints] = useState([]); 18 | 19 | useEffect(()=> { 20 | if (!reqRateSets.length){ 21 | makeDataSets(requestRate); 22 | } 23 | makeDataPoints(requestRate); 24 | console.log('requestRate sets: ', reqRateSets) 25 | console.log('requestRate: ', requestRate) 26 | }, [requestRate]) 27 | 28 | 29 | function makeDataSets(reqData) { 30 | const output = []; 31 | for (let i = 0; i < reqData.length; i++){ 32 | let colorVal = Math.floor(Math.random() * 255) 33 | const obj = { 34 | label: reqData[i].instance, 35 | backgroundColor: `#f39566`, 36 | borderColor: `#f39566`, 37 | fill: false, 38 | data: [], 39 | } 40 | output.push(obj); 41 | } 42 | setReqRateSets(output); 43 | } 44 | 45 | 46 | 47 | function makeDataPoints(newData) { 48 | const newDataPoints = []; 49 | for (let i = 0; i < newData.length; i++) { 50 | newDataPoints.push({x: newData[i].x, y: newData[i].y}); 51 | } 52 | setDataPoints(newDataPoints); 53 | } 54 | 55 | const [anchorEl, setAnchorEl] = useState(null); 56 | 57 | const handleClick = (event) => { 58 | setAnchorEl(event.currentTarget); 59 | }; 60 | 61 | const handleClose = () => { 62 | setAnchorEl(null); 63 | }; 64 | 65 | const open = Boolean(anchorEl); 66 | const id = open ? 'simple-popover' : undefined; 67 | 68 | return ( 69 | 70 | Request Rate 71 | { 89 | chart.data.datasets.forEach((reqRateInstance, index, array) => { 90 | reqRateInstance.data.push({ 91 | x: dataPoints[index].x, 92 | y: dataPoints[index].y 93 | }); 94 | }); 95 | } 96 | } 97 | }, 98 | } 99 | }} 100 | /> 101 | 110 | 120 | The request rate is the rate at which producers send data to brokers. Of course, what constitutes a healthy request rate will vary drastically depending on the use case. Keeping an eye on peaks and drops is essential to ensure continuous service availability. If rate-limiting is not enabled, in the event of a traffic spike, brokers could slow to a crawl as they struggle to process a rapid influx of data. 121 | Source: https://www.datadoghq.com/blog/monitoring-kafka-performance-metrics/ 122 | 123 | 124 | ) 125 | } 126 | export default RequestRate; 127 | -------------------------------------------------------------------------------- /src/Components/ResponseRate.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react' 2 | import { Paper, Box, Typography, Container, Grid, Popover, Button } from '@mui/material'; 3 | import ReadMoreIcon from '@mui/icons-material/ReadMore'; 4 | import Chart from 'chart.js/auto'; 5 | import { Line} from "react-chartjs-2"; 6 | import 'chartjs-adapter-luxon'; 7 | import StreamingPlugin from 'chartjs-plugin-streaming'; 8 | 9 | Chart.register(StreamingPlugin); 10 | 11 | const ResponseRate = ({responseRate}) => { 12 | 13 | const [resRateSets, setResRateSets] = useState([]); 14 | const [dataPoints, setDataPoints] = useState([]); 15 | 16 | useEffect(()=> { 17 | if (!resRateSets.length){ 18 | makeDataSets(responseRate); 19 | } 20 | makeDataPoints(responseRate); 21 | console.log('responseRate sets: ', resRateSets) 22 | console.log('responseRate: ', responseRate) 23 | }, [responseRate]) 24 | 25 | const makeDataSets = resData => { 26 | const output = []; 27 | for (let i = 0; i < resData.length; i++){ 28 | let colorVal = Math.floor(Math.random() * 255) 29 | const obj = { 30 | label: resData[i].instance, 31 | backgroundColor: `#f39566`, 32 | borderColor: `#f39566`, 33 | fill: false, 34 | data: [], 35 | } 36 | output.push(obj); 37 | } 38 | setResRateSets(output); 39 | } 40 | 41 | 42 | function makeDataPoints(newData) { 43 | const newDataPoints = []; 44 | for (let i = 0; i < newData.length; i++) { 45 | newDataPoints.push({x: newData[i].x, y: newData[i].y}); 46 | } 47 | setDataPoints(newDataPoints); 48 | } 49 | 50 | const [anchorEl, setAnchorEl] = useState(null); 51 | 52 | const handleClick = (event) => { 53 | setAnchorEl(event.currentTarget); 54 | }; 55 | 56 | const handleClose = () => { 57 | setAnchorEl(null); 58 | }; 59 | 60 | const open = Boolean(anchorEl); 61 | const id = open ? 'simple-popover' : undefined; 62 | 63 | return ( 64 | 65 | Response Rate 66 | { 85 | chart.data.datasets.forEach((resRateInstance, index, array) => { 86 | resRateInstance.data.push({ 87 | x: dataPoints[index].x, 88 | y: dataPoints[index].y 89 | }); 90 | }); 91 | } 92 | } 93 | }, 94 | } 95 | }} 96 | /> 97 | 106 | 116 | For producers, the response rate represents the rate of responses received from brokers. Brokers respond to producers when the data has been received. Depending on your configuration, “received” could have one of three meanings: 117 | - The message was received, but not committed (request.required.acks == 0) 118 | - The leader has written the message to disk (request.required.acks == 1) 119 | - The leader has received confirmation from all replicas that the data has been written to disk (request.required.acks == all) 120 | Producer data is not available for consumption until the required number of acknowledgments have been received. If you are seeing low response rates, a number of factors could be at play. A good place to start is by checking the request.required.acks configuration directive on your brokers. Choosing the right value for request.required.acks is entirely use case dependent—it’s up to you whether you want to trade availability for consistency. 121 | Source: https://www.datadoghq.com/blog/monitoring-kafka-performance-metrics/ 122 | 123 | 124 | ) 125 | } 126 | export default ResponseRate; -------------------------------------------------------------------------------- /src/Components/SideNav.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Button, TextField, Drawer } from '@mui/material'; 3 | import { useNavigate } from "react-router-dom"; 4 | import axios from 'axios'; 5 | 6 | const SideNav = (props) => { 7 | 8 | const [connectButton, setConnectButton] = React.useState(false); 9 | const navigate = useNavigate(); 10 | 11 | 12 | const button: { color: string, width: string} = { 13 | color: "#a4a4a4", 14 | width: "150px" 15 | } 16 | 17 | const handleSubmit = () => { 18 | const port = (document.getElementById('port') as HTMLInputElement).value; 19 | const domain = (document.getElementById('domain') as HTMLInputElement).value; 20 | localStorage.setItem('port', port); 21 | localStorage.setItem('domain', domain); 22 | props.handlePortAndDomain(domain, port); 23 | setConnectButton(false) 24 | } 25 | 26 | return ( 27 | <> 28 | {connectButton && ( 29 |
30 | 31 | 32 | 33 | 34 | 35 |
36 | )} 37 | {!connectButton && ( 38 |
39 | 40 | 41 | 42 |
43 | )} 44 | 45 | ) 46 | }; 47 | 48 | export default SideNav; -------------------------------------------------------------------------------- /src/Components/SimpleKeyMetrics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect, MyProvider } from 'react' 2 | import { Paper, Box,Typography, Container, Grid, Popover, Button } from '@mui/material'; 3 | import ReadMoreIcon from '@mui/icons-material/ReadMore'; 4 | 5 | const SimpleKeyMetrics = ({ underReplicated, activeControllers, offlinePartitions }) => { 6 | 7 | const [kafkaData, setKafkaData] = useState({ 8 | underReplicatedParsed : [], 9 | activeControllersParsed : [], 10 | offlinePartitionsParsed : [] 11 | }) 12 | let previousValues = useRef({underReplicated, activeControllers, offlinePartitions}) 13 | 14 | useEffect(() => { 15 | if (previousValues.current.activeControllers !== activeControllers && previousValues.current.underReplicated !== underReplicated && previousValues.current.offlinePartitions !== offlinePartitions) { 16 | parseSimpleKeyMetrics( activeControllers, offlinePartitions, underReplicated); 17 | previousValues.current = {underReplicated, activeControllers, offlinePartitions} 18 | console.log('fresh data in simplekeymetrics'); 19 | console.log('underReplicated: ', underReplicated); 20 | console.log('activeControllers: ', activeControllers); 21 | console.log('offlinePartitions: ', offlinePartitions); 22 | } 23 | }); 24 | 25 | function parseSimpleKeyMetrics( actCont, offPart, underRep ) { 26 | let offlinePartSum = 0; 27 | let activeContSum = 0; 28 | let underRepSum = 0; 29 | if ( JSON.stringify(actCont) === '{}' || JSON.stringify(offPart) === '{}' || JSON.stringify(underRep) === '{}') return; 30 | 31 | for (const metricChunk of underRep) { 32 | underRepSum += Number(metricChunk.value[1]); 33 | } 34 | 35 | for (const metricChunk of actCont) { 36 | activeContSum += Number(metricChunk.value[1]); 37 | } 38 | 39 | for (const metricChunk of offPart) { 40 | offlinePartSum += Number(metricChunk.value[1]); 41 | } 42 | 43 | setKafkaData({ 44 | offlinePartitionsParsed: offlinePartSum, 45 | activeControllersParsed: activeContSum, 46 | underReplicatedParsed: underRepSum, 47 | }); 48 | } 49 | 50 | const [anchorElUnderRep, setAnchorElUnderRep] = useState(null); 51 | const [anchorElActiveCont, setAnchorElActiveCont] = useState(null); 52 | const [anchorElOffPart, setAnchorElOffPart] = useState(null); 53 | 54 | const handleClickUnderRep = (event) => { 55 | setAnchorElUnderRep(event.currentTarget); 56 | }; 57 | 58 | const handleClickActiveCont = (event) => { 59 | setAnchorElActiveCont(event.currentTarget); 60 | }; 61 | 62 | const handleClickOffPart = (event) => { 63 | setAnchorElOffPart(event.currentTarget); 64 | }; 65 | 66 | const handleClose = () => { 67 | setAnchorElOffPart(null); 68 | setAnchorElActiveCont(null); 69 | setAnchorElUnderRep(null); 70 | }; 71 | 72 | const openOff = Boolean(anchorElOffPart); 73 | const openActive = Boolean(anchorElActiveCont); 74 | const openUnder = Boolean(anchorElUnderRep); 75 | 76 | const id = (openOff || openActive || openUnder) ? 'simple-popover' : undefined; 77 | 78 | const { offlinePartitionsParsed, activeControllersParsed, underReplicatedParsed } = kafkaData; 79 | 80 | return ( 81 | 82 | 83 | 84 | {/* Offline Partitions */} 85 | 86 | 87 | Offline Partitions 88 | 89 | {offlinePartitionsParsed} 90 | 91 | 101 | This metric reports the number of partitions without an active leader. Because all read and write operations are only performed on partition leaders, you should alert on a non-zero value for this metric to prevent service interruptions. Any partition without an active leader will be completely inaccessible, and both consumers and producers of that partition will be blocked until a leader becomes available. 102 | Source: https://www.datadoghq.com/blog/monitoring-kafka-performance-metrics/ 103 | 104 | 105 | 106 | 107 | 108 | {/* Active Controllers */} 109 | 110 | 111 | Active Controllers 112 | 113 | {activeControllersParsed} 114 | 115 | 125 | The first node to boot in a Kafka cluster automatically becomes the controller, and there can be only one. The controller in a Kafka cluster is responsible for maintaining the list of partition leaders, and coordinating leadership transitions (in the event a partition leader becomes unavailable). If it becomes necessary to replace the controller, ZooKeeper chooses a new controller randomly from the pool of brokers. The sum of ActiveControllerCount across all of your brokers should always equal one, and you should alert on any other value that lasts for longer than one second. 126 | Source: https://www.datadoghq.com/blog/monitoring-kafka-performance-metrics/ 127 | 128 | 129 | 130 | 131 | 132 | 133 | Underreplicated Partitions 134 | 135 | {underReplicatedParsed} 136 | 137 | 147 | In a healthy cluster, the number of in sync replicas (ISRs) should be exactly equal to the total number of replicas. If partition replicas fall too far behind their leaders, the follower partition is removed from the ISR pool, and you should see a corresponding increase in IsrShrinksPerSec. If a broker becomes unavailable, the value of UnderReplicatedPartitions will increase sharply. Since Kafka’s high-availability guarantees cannot be met without replication, investigation is certainly warranted should this metric value exceed zero for extended time periods. 148 | Source: https://www.datadoghq.com/blog/monitoring-kafka-performance-metrics/ 149 | 150 | 151 | 152 | 153 | 154 | 155 | ) 156 | } 157 | export default SimpleKeyMetrics; -------------------------------------------------------------------------------- /src/Components/UnderReplicated.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from 'react' 2 | import { Paper, Box,Typography, Card, Popover } from '@mui/material'; 3 | import ReadMoreIcon from '@mui/icons-material/ReadMore'; 4 | 5 | 6 | const UnderReplicated = ({ underReplicated }) => { 7 | 8 | const [kafkaData, setKafkaData] = useState({ 9 | underReplicatedParsed : [], 10 | }) 11 | 12 | let previousValues = useRef({ underReplicated }) 13 | 14 | useEffect(() => { 15 | if (previousValues.current.underReplicated !== underReplicated) { 16 | parseSimpleKeyMetrics(underReplicated); 17 | previousValues.current = { underReplicated } 18 | console.log('underReplicated: ', underReplicated); 19 | } 20 | }); 21 | 22 | 23 | function parseSimpleKeyMetrics(underRep) { 24 | let underRepSum = 0; 25 | if (JSON.stringify(underRep) === '{}') return; 26 | for (const metricChunk of underRep) { 27 | underRepSum += Number(metricChunk.value[1]); 28 | } 29 | 30 | setKafkaData({ underReplicatedParsed: underRepSum }); 31 | } 32 | 33 | const [anchorEl, setAnchorEl] = useState(null); 34 | 35 | const handleClick = (event) => { 36 | setAnchorEl(event.currentTarget); 37 | }; 38 | 39 | const handleClose = () => { 40 | setAnchorEl(null); 41 | }; 42 | 43 | const open = Boolean(anchorEl); 44 | 45 | const id = (open) ? 'simple-popover' : undefined; 46 | 47 | const { underReplicatedParsed } = kafkaData; 48 | 49 | return ( 50 | 51 | 52 | Underreplicated Partitions 53 | 54 | {underReplicatedParsed} 55 | 64 | 74 | In a healthy cluster, the number of in sync replicas (ISRs) should be exactly equal to the total number of replicas. If partition replicas fall too far behind their leaders, the follower partition is removed from the ISR pool, and you should see a corresponding increase in IsrShrinksPerSec. If a broker becomes unavailable, the value of UnderReplicatedPartitions will increase sharply. Since Kafka’s high-availability guarantees cannot be met without replication, investigation is certainly warranted should this metric value exceed zero for extended time periods. 75 | Source: https://www.datadoghq.com/blog/monitoring-kafka-performance-metrics/ 76 | 77 | 78 | 79 | 80 | ) 81 | } 82 | export default UnderReplicated; -------------------------------------------------------------------------------- /src/Pages/AuthContext.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState, useEffect, createContext } from "react" 2 | import { auth } from "../../firebase.js" 3 | import app from '../../firebase.js'; 4 | import { ConstructionOutlined, Google } from "@mui/icons-material"; 5 | import { getAuth, 6 | createUserWithEmailAndPassword, 7 | signInWithEmailAndPassword, 8 | signOut, 9 | sendPasswordResetEmail, 10 | updateEmail, 11 | updatePassword, 12 | onAuthStateChanged, 13 | GoogleAuthProvider, 14 | signInWithPopup, 15 | GithubAuthProvider 16 | } from "firebase/auth" 17 | const goog = new GoogleAuthProvider() 18 | const github = new GithubAuthProvider(); 19 | const AuthContext = React.createContext() 20 | export function useAuth() { 21 | return useContext(AuthContext) 22 | } 23 | 24 | export function AuthProvider({ children }) { 25 | const [currentUser, setCurrentUser] = useState() 26 | const [loading, setLoading] = useState(true) 27 | 28 | function signup(email, password) { 29 | return createUserWithEmailAndPassword(auth, email, password) 30 | } 31 | function loginWithGoogle(){ 32 | const result = signInWithPopup(auth, goog) 33 | return result; 34 | } 35 | 36 | async function loginWithGithub(){ 37 | const result = await signInWithPopup(auth, github) 38 | return result; 39 | } 40 | function login(email, password) { 41 | return signInWithEmailAndPassword(auth, email, password) 42 | } 43 | 44 | function logout() { 45 | return signOut(auth) 46 | } 47 | 48 | function resetPassword(email) { 49 | return sendPasswordResetEmail(auth, email) 50 | } 51 | 52 | function updateEmail(email) { 53 | return currentUser.updateEmail(email) 54 | } 55 | 56 | function updatePassword(password) { 57 | return currentUser.updatePassword(password) 58 | } 59 | 60 | useEffect(() => { 61 | const unsubscribe = auth.onAuthStateChanged(user => { 62 | setCurrentUser(user) 63 | setLoading(false) 64 | }) 65 | 66 | return unsubscribe 67 | }, []) 68 | 69 | const value = { 70 | currentUser, 71 | loginWithGoogle, 72 | loginWithGithub, 73 | login, 74 | signup, 75 | logout, 76 | resetPassword, 77 | updateEmail, 78 | updatePassword 79 | } 80 | 81 | return ( 82 | 83 | {!loading && children} 84 | 85 | ) 86 | } -------------------------------------------------------------------------------- /src/Pages/ErrorLogDisplay.jsx: -------------------------------------------------------------------------------- 1 | import React,{useEffect, useContext, useState, createContext} from "react"; 2 | import { Grid, Box } from '@mui/material'; 3 | import NavBar from '../Components/NavBar'; 4 | import SideNav from '../Components/SideNav'; 5 | import ErrorLogContainer from '../Components/ErrorLogContainer'; 6 | 7 | export function ErrorLogDisplay () { 8 | 9 | const [loggedIn, setLoggedIn] = useState(true); 10 | 11 | const outerGridContainer = { 12 | display: "grid", 13 | gridTemplateColumns: "200px 1fr", 14 | gridTemplateRows: "60px 1fr", 15 | height: "80vh", 16 | gap: "0px 0px", 17 | gridTemplateAreas: ` 18 | "SideNav NavBar" 19 | "SideNav ErrorLogContainer"` 20 | }; 21 | 22 | return ( 23 | <> 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ) 37 | } 38 | 39 | export default ErrorLogDisplay; -------------------------------------------------------------------------------- /src/Pages/ForgotPassword.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from "react" 2 | import { Alert } from "react-bootstrap" 3 | import { useAuth } from "./AuthContext" 4 | import { Link } from "react-router-dom" 5 | import { Box, Button, TextField } from '@mui/material'; 6 | import logo from '../assets/Hyperion.png'; 7 | 8 | export default function ForgotPassword() { 9 | const emailRef = useRef() 10 | const { resetPassword } = useAuth() 11 | const [error, setError] = useState("") 12 | const [message, setMessage] = useState("") 13 | const [loading, setLoading] = useState(false) 14 | 15 | async function handleSubmit(e) { 16 | e.preventDefault() 17 | 18 | try { 19 | setMessage("") 20 | setError("") 21 | setLoading(true) 22 | await resetPassword(emailRef.current.value) 23 | setMessage("Check your inbox for further instructions") 24 | } catch { 25 | setError("Failed to reset password") 26 | } 27 | 28 | setLoading(false) 29 | } 30 | 31 | return ( 32 |
33 | 34 | 35 | 36 | 37 | 38 |

Reset Password

39 |

Enter email to verify password reset

40 | {error && {error}} 41 | {message && {message}} 42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 |
51 | Need an account? Sign up 52 |
53 |
54 |
55 |
56 | ) 57 | } -------------------------------------------------------------------------------- /src/Pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from "react" 2 | import { Card, Alert } from "react-bootstrap" 3 | import { useAuth } from "./AuthContext"; 4 | import { Link, useHistory, useNavigate } from "react-router-dom" 5 | import { Box, Button, TextField } from '@mui/material'; 6 | import GitHubIcon from "@mui/icons-material/GitHub"; 7 | import GoogleIcon from "@mui/icons-material/Google"; 8 | import logo from '../assets/Hyperion.png'; 9 | 10 | const Login = () => { 11 | const emailRef = useRef(); 12 | const passwordRef = useRef(); 13 | const { login, loginWithGoogle, loginWithGithub } = useAuth() || {}; 14 | const [error, setError] = useState(""); 15 | const [loading, setLoading] = useState(false); 16 | const navigate = useNavigate(); 17 | 18 | async function handleSubmit(e) { 19 | e.preventDefault() 20 | try { 21 | setError("") 22 | setLoading(true) 23 | const result = await login(emailRef.current.value, passwordRef.current.value) 24 | const email = result.user.email; 25 | console.log(email); 26 | localStorage.setItem('email', email ); 27 | localStorage.removeItem("port") 28 | localStorage.removeItem("domain") 29 | navigate("/dashboard") 30 | } catch { 31 | setError("Failed to log in") 32 | } 33 | 34 | setLoading(false) 35 | } 36 | 37 | async function handleGoogle(e) { 38 | e.preventDefault() 39 | 40 | try { 41 | setError("") 42 | setLoading(true) 43 | const result = await loginWithGoogle(); 44 | const email = result.user.email; 45 | console.log(email); 46 | localStorage.setItem('email',email); 47 | localStorage.removeItem("port") 48 | localStorage.removeItem("domain") 49 | navigate("/dashboard") 50 | 51 | } catch { 52 | setError("Failed to log in") 53 | } 54 | 55 | setLoading(false) 56 | } 57 | async function handleGithub(e) { 58 | e.preventDefault() 59 | 60 | try { 61 | setError("") 62 | setLoading(true) 63 | 64 | const result = await loginWithGithub(); 65 | navigate('/dashboard') 66 | } catch { 67 | setError("Failed to log in") 68 | } 69 | 70 | setLoading(false) 71 | } 72 | 73 | return ( 74 |
75 | 76 | 77 | 78 | 79 | 80 |

Hyperion

81 | {error && {error}} 82 |
83 |
84 | 96 |
97 |
98 | 111 |
112 |
113 | 114 | 117 | 120 |
121 |
122 |
123 | Forgot Password? 124 |
125 |
126 | Need an account? Sign Up 127 |
128 |
129 |
130 |
131 | ) 132 | } 133 | 134 | export default Login; -------------------------------------------------------------------------------- /src/Pages/MainDisplay.jsx: -------------------------------------------------------------------------------- 1 | import React,{useEffect, useContext, useState, createContext} from "react"; 2 | import { Grid, Box } from '@mui/material'; 3 | import NavBar from '../Components/NavBar'; 4 | import DataContainer from '../Components/DataContainer'; 5 | import SideNav from '../Components/SideNav'; 6 | import { io, Socket } from "socket.io-client"; 7 | import { Email } from "@mui/icons-material"; 8 | 9 | function MainDisplay () { 10 | 11 | const [domain, setDomain] = useState(null); 12 | const [port, setPort] = useState(null); 13 | const [display, setDisplay] = useState(false); 14 | const [underReplicated, setUnderReplicated] = useState({}); 15 | const [activeControllers, setActiveControllers] = useState({}); 16 | const [offlinePartitions, setOfflinePartitions] = useState({}); 17 | const [avgReqLatency, setAvgReqLatency] = useState({}); 18 | const [responseRate, setResponseRate] = useState({}); 19 | const [requestRate, setRequestRate] = useState({}); 20 | const [producerByteRate, setProducerByteRate] = useState({}); 21 | const [bytesConsumedRate, setBytesConsumedRate] = useState({}); 22 | const email = localStorage.getItem('email'); 23 | 24 | function handlePortAndDomain(newdomain, newport){ 25 | setDomain(newdomain); 26 | setPort(newport); 27 | setSocket() 28 | } 29 | 30 | function setSocket(){ 31 | const domainLocal = localStorage.getItem('domain'); 32 | const portLocal = localStorage.getItem('port'); 33 | if (domainLocal && portLocal) { 34 | const socket = (io('ws://localhost:3500')); 35 | const ip = `${domainLocal}:${portLocal}` 36 | socket.emit("ip&email", ip, email); 37 | 38 | socket.onAny((metric, data) => { 39 | if (metric === 'underReplicated') setUnderReplicated(data); 40 | if (metric === 'offlinePartitions') setOfflinePartitions(data); 41 | if (metric === 'activeControllers') setActiveControllers(data); 42 | if (metric === 'avgReqLatency') setAvgReqLatency(data); 43 | if (metric === 'responseRate') setResponseRate(data); 44 | if (metric === 'requestRate') setRequestRate(data); 45 | if (metric === 'producerByteRate') setProducerByteRate(data); 46 | if (metric === 'bytesConsumedRate') setBytesConsumedRate(data); 47 | }) 48 | } 49 | } 50 | 51 | 52 | useEffect(()=> { 53 | const domainLocal = localStorage.getItem('domain'); 54 | const portLocal = localStorage.getItem('port'); 55 | 56 | if (domainLocal && portLocal) { 57 | const socket = (io('ws://localhost:3500')); 58 | const ip = `${domainLocal}:${portLocal}` 59 | socket.emit("ip&email", ip, email); 60 | 61 | socket.onAny((metric, data) => { 62 | if (metric === 'underReplicated') setUnderReplicated(data); 63 | if (metric === 'offlinePartitions') setOfflinePartitions(data); 64 | if (metric === 'activeControllers') setActiveControllers(data); 65 | if (metric === 'avgReqLatency') setAvgReqLatency(data); 66 | if (metric === 'responseRate') setResponseRate(data); 67 | if (metric === 'requestRate') setRequestRate(data); 68 | if (metric === 'producerByteRate') setProducerByteRate(data); 69 | if (metric === 'bytesConsumedRate') setBytesConsumedRate(data); 70 | }) 71 | } 72 | }, []) 73 | 74 | const outerGridContainer = { 75 | display: "grid", 76 | gridTemplateColumns: "200px 1fr", 77 | gridTemplateRows: "60px 1fr", 78 | height: "80vh", 79 | gap: "0px 0px", 80 | gridTemplateAreas: ` 81 | "SideNav NavBar" 82 | "SideNav DataContainer"` 83 | }; 84 | 85 | return ( 86 | <> 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 105 | 106 | 107 | 108 | ) 109 | } 110 | 111 | export default MainDisplay; 112 | 113 | -------------------------------------------------------------------------------- /src/Pages/PrivateRoute.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Navigate, Outlet } from 'react-router-dom'; 3 | 4 | const PrivateRoute = () => { 5 | const auth = null; 6 | return auth ? : ; 7 | } 8 | export default PrivateRoute; -------------------------------------------------------------------------------- /src/Pages/Signup.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from "react"; 2 | import { Alert } from "react-bootstrap"; 3 | import { Box, Button, TextField } from '@mui/material'; 4 | import { useAuth } from "./AuthContext"; 5 | import { Link, useNavigate } from "react-router-dom"; 6 | import GitHubIcon from "@mui/icons-material/GitHub"; 7 | import GoogleIcon from "@mui/icons-material/Google"; 8 | import logo from '../assets/Hyperion.png'; 9 | 10 | export default function Signup() { 11 | const emailRef = useRef(); 12 | const passwordRef = useRef(); 13 | const passwordConfirmRef = useRef(); 14 | const { signup } = useAuth() || {} 15 | const [error, setError] = useState(""); 16 | const [loading, setLoading] = useState(false); 17 | const navigate = useNavigate(); 18 | 19 | 20 | async function handleSubmit(e) { 21 | e.preventDefault(); 22 | 23 | if (passwordRef.current.value !== passwordConfirmRef.current.value) { 24 | return setError("Passwords do not match"); 25 | } 26 | 27 | try { 28 | console.log('email', emailRef.current.value) 29 | console.log('password', passwordRef.current.value) 30 | setError("") 31 | setLoading(true) 32 | await signup(emailRef.current.value, passwordRef.current.value) 33 | navigate("/dashboard") 34 | } catch { 35 | setError("Failed to create an account") 36 | } 37 | 38 | setLoading(false) 39 | } 40 | 41 | return ( 42 |
43 | 44 | 45 | 46 | 47 | 48 |

Hyperion

49 | {error && {error}} 50 |
51 |
52 | 53 |
54 |
55 | 56 |
57 |
58 | 59 |
60 |
61 | 62 | 65 | 68 |
69 |
70 |
71 | Already have an account? Log in 72 |
73 |
74 |
75 |
76 | ) 77 | } -------------------------------------------------------------------------------- /src/app.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Nanum+Gothic&display=swap'); 2 | 3 | $white: #ffffff; 4 | $lightGrey: #f6f6f6; 5 | $mediumGrey: #ececec; 6 | $darkGrey: #a4a4a4; 7 | $textGrey: #2d2d2d; 8 | $logoOrange: #f3be66; 9 | $logoMidOrange: #f39566; 10 | $logoMidPink: #f366dc; 11 | $logoPurple: #ce10fa; 12 | $dustyPurple: #63489b; 13 | $darkPurple: #120a27; 14 | 15 | 16 | body { 17 | margin: 0; 18 | padding: 0; 19 | color: $white; 20 | font-family: "Nanum Gothic", sans-serif; 21 | box-sizing: border-box; 22 | } 23 | 24 | @media (min-width: 1200px) { 25 | .css-1oqqzyl-MuiContainer-root { 26 | max-width: 0px; 27 | } 28 | } 29 | 30 | @media (min-width: 600px) { 31 | .css-1oqqzyl-MuiContainer-root { 32 | padding-left: 0px; 33 | padding-right: 0px; 34 | } 35 | } 36 | 37 | .css-1oqqzyl-MuiContainer-root { 38 | width: 100%; 39 | margin-left: auto; 40 | background: red; 41 | box-sizing: border-box; 42 | margin-right: auto; 43 | display: block; 44 | padding-left: 0px; 45 | padding-right: 0px; 46 | } 47 | 48 | .main-app { 49 | width: 100vw; 50 | background: $logoMidPink; 51 | } 52 | 53 | 54 | #NavBar { 55 | grid-area: NavBar; 56 | height:60px; 57 | background-color: $darkGrey; 58 | display: flex; 59 | flex-direction: row; 60 | justify-content: flex-end; 61 | align-items: center; 62 | box-shadow: none; 63 | padding: 5px; 64 | } 65 | 66 | #leftSide { 67 | justify-content: flex-start; 68 | } 69 | 70 | #rightSide { 71 | justify-content: flex-end; 72 | } 73 | 74 | .side-nav { 75 | grid-area: SideNav; 76 | height: 100%; 77 | background-color: $mediumGrey; 78 | display: flex; 79 | flex-direction: column; 80 | justify-content: left; 81 | align-items: center; 82 | padding-top: 150px; 83 | width: 100%; 84 | align-content: center; 85 | float: left; 86 | } 87 | 88 | .key-metrics { 89 | display: flex; 90 | flex-direction: row; 91 | justify-content: space-around; 92 | padding: 10px; 93 | } 94 | 95 | .login-page { 96 | display: flex; 97 | height: 100vh; 98 | width: 100vw; 99 | flex-direction: row; 100 | flex-wrap: wrap; 101 | justify-content: center; 102 | align-items: center; 103 | background-color: $darkGrey; 104 | } 105 | 106 | .logo-box{ 107 | background: $mediumGrey; 108 | width: 50%; 109 | height: 100%; 110 | box-sizing: border-box; 111 | display: flex; 112 | justify-content: center; 113 | align-items: center; 114 | align-content: center; 115 | border-radius: 5px 0px 0px 5px; 116 | } 117 | 118 | .login-box { 119 | background-color: white; 120 | border-radius: 5px; 121 | color: $textGrey; 122 | height: 70vh; 123 | width: 70vw; 124 | box-shadow: 0px 0px 34px -12px $textGrey; 125 | display: flex; 126 | flex-direction: row; 127 | align-content: center; 128 | align-items: center; 129 | justify-content: center; 130 | } 131 | 132 | .login-text-box { 133 | display: flex; 134 | flex-direction: column; 135 | align-content: center; 136 | align-items: center; 137 | justify-content: center; 138 | width:50%; 139 | } 140 | 141 | .form-button { 142 | display: flex; 143 | flex-direction: column; 144 | align-content: center; 145 | align-items: center; 146 | justify-content: center; 147 | } 148 | 149 | 150 | .DataContainer { 151 | grid-area: DataContainer; 152 | display: flex; 153 | color: $textGrey; 154 | height:100%; 155 | align-items: center; 156 | justify-content: center; 157 | text-align: center; 158 | background-color: $white; 159 | } 160 | 161 | .paper { 162 | box-shadow: none; 163 | padding: 5px; 164 | border: 1px solid #d0d0d0; 165 | } 166 | 167 | .logo { 168 | animation: bounce 1s; 169 | animation-direction: alternate; 170 | animation-iteration-count: infinite; 171 | } 172 | 173 | @keyframes bounce { 174 | from { 175 | transform: translate3d(0, 0, 0); 176 | } 177 | to { 178 | transform: translate3d(0, 10px, 0); 179 | } 180 | } 181 | 182 | .data-label { 183 | color: $darkGrey; 184 | letter-spacing: 2px; 185 | text-align: center; 186 | } 187 | 188 | .big-number { 189 | color: $logoMidOrange; 190 | } 191 | 192 | .form-input { 193 | padding: 10px; 194 | color: $textGrey; 195 | } 196 | 197 | .button { 198 | background-color: white; 199 | color: $logoOrange; 200 | border-radius: 3px; 201 | padding: 10px; 202 | display: flex; 203 | flex-direction: row; 204 | align-items: center; 205 | justify-content: center; 206 | width: 100px; 207 | } 208 | 209 | Link:link { 210 | color: $darkPurple; 211 | } 212 | 213 | Link:visited { 214 | color: green; 215 | } 216 | 217 | Link:hover { 218 | color: hotpink; 219 | } -------------------------------------------------------------------------------- /src/assets/Hyperion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Hyperionn/0f56d421def6c3ffcf09bb946ebc69aa4a63b764/src/assets/Hyperion.png -------------------------------------------------------------------------------- /src/assets/LoginLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Hyperionn/0f56d421def6c3ffcf09bb946ebc69aa4a63b764/src/assets/LoginLogo.png -------------------------------------------------------------------------------- /src/assets/SpaceLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Hyperionn/0f56d421def6c3ffcf09bb946ebc69aa4a63b764/src/assets/SpaceLogo.png -------------------------------------------------------------------------------- /src/assets/connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Hyperionn/0f56d421def6c3ffcf09bb946ebc69aa4a63b764/src/assets/connect.png -------------------------------------------------------------------------------- /src/assets/errorlog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Hyperionn/0f56d421def6c3ffcf09bb946ebc69aa4a63b764/src/assets/errorlog.png -------------------------------------------------------------------------------- /src/assets/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Hyperionn/0f56d421def6c3ffcf09bb946ebc69aa4a63b764/src/assets/login.png -------------------------------------------------------------------------------- /src/assets/moredash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Hyperionn/0f56d421def6c3ffcf09bb946ebc69aa4a63b764/src/assets/moredash.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hyperion 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | 5 | const root = ReactDOM.createRoot(document.getElementById('root')); 6 | 7 | root.render( 8 | 9 | 10 | 11 | ); -------------------------------------------------------------------------------- /src/setuptTest.js: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom"; 2 | 3 | vi.mock('react-chartjs-2', () => ({ 4 | Line: () => null 5 | })); -------------------------------------------------------------------------------- /src/test/AvgRequestLatency.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {render, screen} from '@testing-library/react'; 3 | import {describe, it, expect, test} from 'vitest' 4 | import "@testing-library/jest-dom"; 5 | vi.mock('react-chartjs-2', () => ({ 6 | Line: () => null 7 | })); 8 | 9 | 10 | //import { BrowserRouter} from 'react-router-dom'; 11 | import AvgRequestLatency, {makeDataPoints} from '../Components/AvgRequestLatency'; 12 | 13 | 14 | const avgReqLatency = {x: 1, y : 2} 15 | const dataPoints = [{x:2,y:2}] 16 | describe('average request latency takes in state', () => { 17 | 18 | it('renders and expects props', async () => { 19 | render() 20 | expect(screen.getByRole('name')).toBeInTheDocument(); 21 | }) 22 | 23 | }) -------------------------------------------------------------------------------- /src/test/Login.test.jsx: -------------------------------------------------------------------------------- 1 | 2 | import "@testing-library/jest-dom"; 3 | import React from 'react' 4 | import {render, screen} from '@testing-library/react'; 5 | import {describe, it, expect, test} from 'vitest' 6 | import { BrowserRouter} from 'react-router-dom'; 7 | 8 | import Login from '../Pages/Login'; 9 | 10 | describe("please work", () => { 11 | test("Login Rendering", () => { 12 | render( 13 | 14 | ); 15 | const google = screen.getByRole("google-button"); 16 | const login = screen.getByRole("login-button"); 17 | const github = screen.getByRole("github-button"); 18 | expect(google).toBeInTheDocument(); 19 | expect(login).toBeInTheDocument(); 20 | expect(github).toBeInTheDocument(); 21 | }) 22 | }) 23 | 24 | describe('Addition', () => { 25 | it('knows that 2 and 2 make 4', () => { 26 | expect(2 + 2).toBe(4); 27 | }); 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /src/test/OfflinePartitions.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {render, screen, cleanup } from '@testing-library/react'; 3 | import {describe, it, expect, test} from 'vitest' 4 | import OfflinePartitions from '../Components/OfflinePartitions'; 5 | import ReadMoreIcon from '@mui/icons-material/ReadMore'; 6 | import "@testing-library/jest-dom"; 7 | 8 | const props = {offlinePartitions : 1} 9 | 10 | describe ('offline Partitions', () => { 11 | afterEach(cleanup) 12 | test('it renders', async () => { 13 | render( 14 | , 15 | ) 16 | expect(await screen.getByRole('title')).toHaveTextContent('Offline Partitions'); 17 | }) 18 | }) -------------------------------------------------------------------------------- /src/test/SignUp.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render, screen, cleanup} from '@testing-library/react'; 3 | import Signup from '../Pages/Signup.jsx' 4 | import { describe, expect, it, vi, beforeEach, afterEach, test} from 'vitest'; 5 | import { BrowserRouter} from 'react-router-dom'; 6 | import userEvent from '@testing-library/user-event'; 7 | import "@testing-library/jest-dom"; 8 | 9 | 10 | 11 | describe('sign up works', async() => { 12 | 13 | afterEach(cleanup) 14 | 15 | test("Login Rendering", () => { 16 | render( 17 | 18 | ) 19 | const email = screen.getByRole('emailText'); 20 | const password = screen.getByRole('password'); 21 | const confirmPassword = screen.getByRole("confirmPassword"); 22 | const signUpBttn = screen.getByRole('signUp') 23 | expect(email).toBeInTheDocument(); 24 | expect(email.value).toBe(undefined) 25 | expect(password).toBeInTheDocument(); 26 | expect(password.value).toBe(undefined) 27 | expect(confirmPassword).toBeInTheDocument(); 28 | expect(confirmPassword.value).toBe(undefined) 29 | expect(signUpBttn).toBeInTheDocument() 30 | }) 31 | 32 | test(('test'), async () => { 33 | render( 34 | 35 | ) 36 | const email = await screen.getByRole('textbox', {name : /email/i}); 37 | 38 | await userEvent.type(email, 'tonysoprano@gmail.com') 39 | expect(await email.value).toBe('tonysoprano@gmail.com') 40 | 41 | }) 42 | }) 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "jsx": "react-jsx", 5 | "outDir": "./dist", 6 | "allowJs": true, 7 | "esModuleInterop": true, 8 | }, 9 | "include": ["./src/**/*"], 10 | "exclude": ["node_modules"] 11 | } 12 | 13 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | test: { 7 | globals: true, 8 | environment: 'jsdom', 9 | //setupFiles: ["./src/setupTest.js"], 10 | }, 11 | esbuild: { 12 | loader: "jsx", 13 | include: /src\/.*\.jsx?$/, 14 | exclude: [], 15 | jsxFactory: 'h', 16 | jsxFragment: 'Fragment' 17 | }, 18 | assetsInclude: ['**/*.gltf'], 19 | 20 | }) -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | const config = { 6 | entry: [ 7 | './src/index.tsx' 8 | ], 9 | mode: process.env.NODE_ENV, 10 | output: { 11 | path: path.resolve(__dirname, 'dist'), 12 | filename: 'bundle.js' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /jsx?$/, 18 | exclude: /node_modules/, 19 | loader: 'babel-loader', 20 | options: { 21 | presets: ['@babel/env', '@babel/react'], 22 | plugins: ['@babel/plugin-transform-runtime', '@babel/transform-async-to-generator'], 23 | }, 24 | }, 25 | { 26 | test: /\.tsx?$/, 27 | exclude: /node_modules/, 28 | use: { 29 | loader: 'ts-loader', 30 | }, 31 | }, 32 | { 33 | test: /\.s?css/, 34 | exclude: /node_modules/, 35 | use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'], 36 | }, 37 | { 38 | test: /\.(jpe?g|png|gif|svg)$/i, 39 | loader: 'file-loader', 40 | options: { 41 | name: 'src/assets/[name].[ext]' 42 | } 43 | }, 44 | 45 | ], 46 | }, 47 | devServer: { 48 | static: { 49 | publicPath: '/', 50 | directory: path.resolve(__dirname, 'src'), 51 | }, 52 | proxy: { 53 | '/server': { 54 | target: 'http://localhost:3001', 55 | secure: false, 56 | }, 57 | }, 58 | }, 59 | plugins: [ 60 | new HtmlWebpackPlugin({ 61 | filename: 'index.html', 62 | template: path.resolve(__dirname, './src/index.html') 63 | }), 64 | ], 65 | resolve: { 66 | extensions: ['.js', '.jsx', '.ts', '.tsx'] 67 | } 68 | }; 69 | 70 | module.exports = config; 71 | --------------------------------------------------------------------------------