├── src ├── web │ ├── .env │ ├── .eslintrc.js │ ├── .dockerignore │ ├── public │ │ ├── favicon.ico │ │ ├── apple-icon.png │ │ ├── manifest.json │ │ └── index.html │ ├── src │ │ ├── images │ │ │ ├── logo.jpg │ │ │ ├── logo.png │ │ │ ├── FullCycleReact.png │ │ │ ├── fullcycle_reset.png │ │ │ ├── fullcycle_switch.png │ │ │ └── Full Cycle Mining Logo.png │ │ ├── assets │ │ │ ├── img │ │ │ │ ├── mask.png │ │ │ │ ├── cover.jpeg │ │ │ │ ├── favicon.png │ │ │ │ ├── new_logo.png │ │ │ │ ├── reactlogo.png │ │ │ │ ├── sidebar-1.jpg │ │ │ │ ├── sidebar-2.jpg │ │ │ │ ├── sidebar-3.jpg │ │ │ │ ├── sidebar-4.jpg │ │ │ │ ├── tim_80x80.png │ │ │ │ ├── apple-icon.png │ │ │ │ └── faces │ │ │ │ │ └── marc.jpg │ │ │ ├── jss │ │ │ │ ├── material-dashboard-react │ │ │ │ │ ├── dashboardStyle.jsx │ │ │ │ │ ├── iconsStyle.jsx │ │ │ │ │ ├── appStyle.jsx │ │ │ │ │ ├── regularCardStyle.jsx │ │ │ │ │ ├── footerStyle.jsx │ │ │ │ │ ├── profileCardStyle.jsx │ │ │ │ │ ├── tableStyle.jsx │ │ │ │ │ ├── typographyStyle.jsx │ │ │ │ │ ├── customInputStyle.jsx │ │ │ │ │ ├── headerStyle.jsx │ │ │ │ │ ├── snackbarContentStyle.jsx │ │ │ │ │ ├── tasksCardStyle.jsx │ │ │ │ │ ├── chartCardStyle.jsx │ │ │ │ │ ├── tasksStyle.jsx │ │ │ │ │ ├── statsCardStyle.jsx │ │ │ │ │ ├── headerLinksStyle.jsx │ │ │ │ │ ├── iconButtonStyle.jsx │ │ │ │ │ ├── buttonStyle.jsx │ │ │ │ │ └── sidebarStyle.jsx │ │ │ │ └── material-dashboard-react.jsx │ │ │ └── css │ │ │ │ └── material-dashboard-react.css │ │ ├── routes │ │ │ ├── index.jsx │ │ │ └── dashboard.jsx │ │ ├── components │ │ │ ├── Grid │ │ │ │ └── ItemGrid.jsx │ │ │ ├── CustomButtons │ │ │ │ ├── DonateButton.jsx │ │ │ │ ├── IconButton.jsx │ │ │ │ └── Button.jsx │ │ │ ├── Typography │ │ │ │ ├── P.jsx │ │ │ │ ├── A.jsx │ │ │ │ ├── Info.jsx │ │ │ │ ├── Muted.jsx │ │ │ │ ├── Small.jsx │ │ │ │ ├── Danger.jsx │ │ │ │ ├── Primary.jsx │ │ │ │ ├── Success.jsx │ │ │ │ ├── Warning.jsx │ │ │ │ └── Quote.jsx │ │ │ ├── Footer │ │ │ │ └── Footer.jsx │ │ │ ├── Snackbar │ │ │ │ ├── SnackbarContent.jsx │ │ │ │ └── Snackbar.jsx │ │ │ ├── Cards │ │ │ │ ├── ProfileCard.jsx │ │ │ │ ├── RegularCard.jsx │ │ │ │ ├── ChartCard.jsx │ │ │ │ ├── StatsCard.jsx │ │ │ │ └── TasksCard.jsx │ │ │ ├── Header │ │ │ │ ├── Header.jsx │ │ │ │ └── HeaderLinks.jsx │ │ │ ├── Table │ │ │ │ └── Table.jsx │ │ │ ├── CustomInput │ │ │ │ └── CustomInput.jsx │ │ │ ├── index.js │ │ │ ├── Tasks │ │ │ │ └── Tasks.jsx │ │ │ └── Sidebar │ │ │ │ └── Sidebar.jsx │ │ ├── views │ │ │ ├── Sensors │ │ │ │ ├── icons │ │ │ │ │ ├── humidity.svg │ │ │ │ │ ├── camera.svg │ │ │ │ │ └── temperature.svg │ │ │ │ ├── SensorList.jsx │ │ │ │ ├── CameraButton.jsx │ │ │ │ └── Sensors.jsx │ │ │ ├── Dashboard │ │ │ │ ├── Dashboard.jsx │ │ │ │ ├── MiningCommands.jsx │ │ │ │ └── MinerSummary.jsx │ │ │ ├── Notifications │ │ │ │ └── Notifications.jsx │ │ │ ├── Pools │ │ │ │ ├── Pools.jsx │ │ │ │ └── PoolsTable.jsx │ │ │ ├── Icons │ │ │ │ └── Icons.jsx │ │ │ ├── TableList │ │ │ │ └── TableList.jsx │ │ │ ├── About │ │ │ │ └── About.jsx │ │ │ ├── Miners │ │ │ │ └── Miners.jsx │ │ │ ├── UserProfile │ │ │ │ └── UserProfile.jsx │ │ │ └── Typography │ │ │ │ └── Typography.jsx │ │ ├── index.js │ │ ├── variables │ │ │ ├── general.jsx │ │ │ └── charts.jsx │ │ ├── logo.svg │ │ └── layouts │ │ │ └── Dashboard │ │ │ └── Dashboard.jsx │ ├── Dockerfile │ └── package.json ├── api │ ├── .dockerignore │ ├── src │ │ ├── services.js │ │ ├── messages.js │ │ ├── api.js │ │ └── server.js │ ├── Dockerfile │ └── package.json ├── Docker.md └── xpackage.json ├── .eslintrc.js ├── .vscode └── launch.json ├── Dockerfile ├── LICENSE ├── .gitignore └── README.md /src/web/.env: -------------------------------------------------------------------------------- 1 | NODE_PATH=./src 2 | REACT_APP_DONATE_AMOUNT=10 3 | -------------------------------------------------------------------------------- /src/web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "airbnb" 3 | }; -------------------------------------------------------------------------------- /src/api/.dockerignore: -------------------------------------------------------------------------------- 1 | 2 | .git 3 | *Dockerfile* 4 | *docker-compose* 5 | node_modules 6 | -------------------------------------------------------------------------------- /src/web/.dockerignore: -------------------------------------------------------------------------------- 1 | 2 | .git 3 | *Dockerfile* 4 | *docker-compose* 5 | node_modules 6 | -------------------------------------------------------------------------------- /src/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/public/favicon.ico -------------------------------------------------------------------------------- /src/web/public/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/public/apple-icon.png -------------------------------------------------------------------------------- /src/web/src/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/images/logo.jpg -------------------------------------------------------------------------------- /src/web/src/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/images/logo.png -------------------------------------------------------------------------------- /src/web/src/assets/img/mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/assets/img/mask.png -------------------------------------------------------------------------------- /src/web/src/assets/img/cover.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/assets/img/cover.jpeg -------------------------------------------------------------------------------- /src/web/src/assets/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/assets/img/favicon.png -------------------------------------------------------------------------------- /src/web/src/assets/img/new_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/assets/img/new_logo.png -------------------------------------------------------------------------------- /src/web/src/assets/img/reactlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/assets/img/reactlogo.png -------------------------------------------------------------------------------- /src/web/src/assets/img/sidebar-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/assets/img/sidebar-1.jpg -------------------------------------------------------------------------------- /src/web/src/assets/img/sidebar-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/assets/img/sidebar-2.jpg -------------------------------------------------------------------------------- /src/web/src/assets/img/sidebar-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/assets/img/sidebar-3.jpg -------------------------------------------------------------------------------- /src/web/src/assets/img/sidebar-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/assets/img/sidebar-4.jpg -------------------------------------------------------------------------------- /src/web/src/assets/img/tim_80x80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/assets/img/tim_80x80.png -------------------------------------------------------------------------------- /src/web/src/assets/img/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/assets/img/apple-icon.png -------------------------------------------------------------------------------- /src/web/src/assets/img/faces/marc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/assets/img/faces/marc.jpg -------------------------------------------------------------------------------- /src/web/src/images/FullCycleReact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/images/FullCycleReact.png -------------------------------------------------------------------------------- /src/web/src/images/fullcycle_reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/images/fullcycle_reset.png -------------------------------------------------------------------------------- /src/web/src/images/fullcycle_switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/images/fullcycle_switch.png -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "airbnb-base", 3 | "parserOptions": { 4 | "ecmaVersion": 6 5 | } 6 | }; -------------------------------------------------------------------------------- /src/web/src/images/Full Cycle Mining Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfoderick/fullcyclereact/HEAD/src/web/src/images/Full Cycle Mining Logo.png -------------------------------------------------------------------------------- /src/web/src/routes/index.jsx: -------------------------------------------------------------------------------- 1 | import Dashboard from "layouts/Dashboard/Dashboard.jsx"; 2 | 3 | const indexRoutes = [{ path: "/", component: Dashboard }]; 4 | 5 | export default indexRoutes; 6 | -------------------------------------------------------------------------------- /src/web/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/api/src/services.js: -------------------------------------------------------------------------------- 1 | 2 | const config = {}; 3 | 4 | config.web = {}; 5 | config.redis = {}; 6 | config.messagebus = {}; 7 | 8 | config.web.host = 'localhost'; 9 | config.web.port = process.env.PORT || 5000; 10 | config.messagebus.connection = `amqp://fullcycle:mining@${config.web.host}`; 11 | config.redis.host = config.web.host; 12 | config.redis.port = 6379; 13 | config.redis.password = ''; 14 | 15 | module.exports = config; 16 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/dashboardStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // Dashboard styles 3 | // ############################# 4 | 5 | import { successColor } from "assets/jss/material-dashboard-react.jsx"; 6 | 7 | const dashboardStyle = { 8 | successText: { 9 | color: successColor 10 | }, 11 | upArrowCardCategory: { 12 | width: 14, 13 | height: 14 14 | } 15 | }; 16 | 17 | export default dashboardStyle; 18 | -------------------------------------------------------------------------------- /src/web/src/components/Grid/ItemGrid.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles, Grid } from "material-ui"; 3 | 4 | const style = { 5 | grid: { 6 | padding: "0 15px !important" 7 | } 8 | }; 9 | 10 | function ItemGrid({ ...props }) { 11 | const { classes, children, ...rest } = props; 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | 19 | export default withStyles(style)(ItemGrid); 20 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/iconsStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // Icons styles 3 | // ############################# 4 | 5 | import { boxShadow } from "assets/jss/material-dashboard-react.jsx"; 6 | 7 | const iconsStyle = { 8 | iframe: { 9 | width: "100%", 10 | height: "500px", 11 | border: "0", 12 | ...boxShadow 13 | }, 14 | iframeContainer: { 15 | margin: "0 -20px 0" 16 | } 17 | }; 18 | 19 | export default iconsStyle; 20 | -------------------------------------------------------------------------------- /src/api/Dockerfile: -------------------------------------------------------------------------------- 1 | #this docker currently not used see /src/Dockerfile 2 | 3 | FROM arm32v7/node:9 4 | 5 | # Install Node.js and other dependencies 6 | RUN apt-get update && \ 7 | apt-get -y install curl && \ 8 | apt-get -y install python build-essential 9 | RUN npm install -g nodemon 10 | 11 | #first copy package and install dependencies 12 | WORKDIR /usr/src/fullcyclereact/src/api/ 13 | COPY package*.json ./ 14 | RUN npm install 15 | 16 | #then copy source 17 | COPY . . 18 | 19 | EXPOSE 5000 20 | 21 | CMD npm run prod 22 | -------------------------------------------------------------------------------- /src/web/src/views/Sensors/icons/humidity.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/web/src/components/CustomButtons/DonateButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | //import MoneyButton from 'react-money-button' 3 | import MoneyButton from '@moneybutton/react-money-button' 4 | 5 | class DonateButton extends Component { 6 | render() { 7 | let amt = process.env.REACT_APP_DONATE_AMOUNT; 8 | return ( 9 | 15 | ) 16 | } 17 | } 18 | 19 | export default DonateButton; -------------------------------------------------------------------------------- /src/web/src/components/Typography/P.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles } from "material-ui"; 3 | import PropTypes from "prop-types"; 4 | 5 | import typographyStyle from "assets/jss/material-dashboard-react/typographyStyle.jsx"; 6 | 7 | function P({ ...props }) { 8 | const { classes, children } = props; 9 | return ( 10 |

11 | {children} 12 |

13 | ); 14 | } 15 | 16 | P.propTypes = { 17 | classes: PropTypes.object.isRequired 18 | }; 19 | 20 | export default withStyles(typographyStyle)(P); 21 | -------------------------------------------------------------------------------- /src/Docker.md: -------------------------------------------------------------------------------- 1 | 2 | Get bash prompt inside container 3 | ``` 4 | docker run -it dfoderick/fullcyclereact /bin/bash 5 | ``` 6 | 7 | # Build for Docker: 8 | git clone 9 | docker login 10 | cd fullcyclereact/ 11 | docker build -t fullcycle/web . 12 | docker push fullcycle/web:latest 13 | 14 | Install: 15 | docker stop fullcycleapi fullcycleweb 16 | docker rm fullcycleapi fullcycleweb 17 | 18 | !don't think -i should be passed here 19 | //docker run --name fullcycleapi -d --network=host --restart unless-stopped fullcycle/api 20 | docker run --name fullcycleweb -d --network=host --restart unless-stopped fullcycle/web 21 | -------------------------------------------------------------------------------- /src/web/Dockerfile: -------------------------------------------------------------------------------- 1 | #this docker currently not used see /src/Dockerfile 2 | 3 | FROM arm32v7/node:9 4 | 5 | RUN apt-get update && \ 6 | apt-get -y install curl && \ 7 | apt-get -y install python build-essential 8 | RUN npm install -g nodemon 9 | RUN npm install -g serve 10 | 11 | #first copy just the package and install dependencies 12 | WORKDIR /usr/src/fullcyclereact/src/web/ 13 | COPY package*.json ./ 14 | RUN npm install 15 | RUN npm install material-ui@next 16 | 17 | #then copy source 18 | COPY . . 19 | RUN npm run build 20 | 21 | EXPOSE 3000 22 | 23 | #CMD npm run start 24 | CMD serve -p 3000 build 25 | -------------------------------------------------------------------------------- /src/web/src/components/Typography/A.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles } from "material-ui"; 3 | import PropTypes from "prop-types"; 4 | 5 | import typographyStyle from "assets/jss/material-dashboard-react/typographyStyle.jsx"; 6 | 7 | function A({ ...props }) { 8 | const { classes, children, ...rest } = props; 9 | return ( 10 | 11 | {children} 12 | 13 | ); 14 | } 15 | 16 | A.propTypes = { 17 | classes: PropTypes.object.isRequired 18 | }; 19 | 20 | export default withStyles(typographyStyle)(A); 21 | -------------------------------------------------------------------------------- /src/web/src/components/Typography/Info.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { withStyles } from "material-ui"; 4 | 5 | import typographyStyle from "assets/jss/material-dashboard-react/typographyStyle.jsx"; 6 | 7 | function Info({ ...props }) { 8 | const { classes, children } = props; 9 | return ( 10 |
11 | {children} 12 |
13 | ); 14 | } 15 | 16 | Info.propTypes = { 17 | classes: PropTypes.object.isRequired 18 | }; 19 | 20 | export default withStyles(typographyStyle)(Info); 21 | -------------------------------------------------------------------------------- /src/web/src/components/Typography/Muted.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { withStyles } from "material-ui"; 4 | 5 | import typographyStyle from "assets/jss/material-dashboard-react/typographyStyle.jsx"; 6 | 7 | function Muted({ ...props }) { 8 | const { classes, children } = props; 9 | return ( 10 |
11 | {children} 12 |
13 | ); 14 | } 15 | 16 | Muted.propTypes = { 17 | classes: PropTypes.object.isRequired 18 | }; 19 | 20 | export default withStyles(typographyStyle)(Muted); 21 | -------------------------------------------------------------------------------- /src/web/src/components/Typography/Small.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { withStyles } from "material-ui"; 4 | 5 | import typographyStyle from "assets/jss/material-dashboard-react/typographyStyle.jsx"; 6 | 7 | function Small({ ...props }) { 8 | const { classes, children } = props; 9 | return ( 10 |
11 | {children} 12 |
13 | ); 14 | } 15 | 16 | Small.propTypes = { 17 | classes: PropTypes.object.isRequired 18 | }; 19 | 20 | export default withStyles(typographyStyle)(Small); 21 | -------------------------------------------------------------------------------- /src/web/src/components/Typography/Danger.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { withStyles } from "material-ui"; 4 | 5 | import typographyStyle from "assets/jss/material-dashboard-react/typographyStyle.jsx"; 6 | 7 | function Danger({ ...props }) { 8 | const { classes, children } = props; 9 | return ( 10 |
11 | {children} 12 |
13 | ); 14 | } 15 | 16 | Danger.propTypes = { 17 | classes: PropTypes.object.isRequired 18 | }; 19 | 20 | export default withStyles(typographyStyle)(Danger); 21 | -------------------------------------------------------------------------------- /src/web/src/components/Typography/Primary.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { withStyles } from "material-ui"; 4 | 5 | import typographyStyle from "assets/jss/material-dashboard-react/typographyStyle.jsx"; 6 | 7 | function Primary({ ...props }) { 8 | const { classes, children } = props; 9 | return ( 10 |
11 | {children} 12 |
13 | ); 14 | } 15 | 16 | Primary.propTypes = { 17 | classes: PropTypes.object.isRequired 18 | }; 19 | 20 | export default withStyles(typographyStyle)(Primary); 21 | -------------------------------------------------------------------------------- /src/web/src/components/Typography/Success.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { withStyles } from "material-ui"; 4 | 5 | import typographyStyle from "assets/jss/material-dashboard-react/typographyStyle.jsx"; 6 | 7 | function Success({ ...props }) { 8 | const { classes, children } = props; 9 | return ( 10 |
11 | {children} 12 |
13 | ); 14 | } 15 | 16 | Success.propTypes = { 17 | classes: PropTypes.object.isRequired 18 | }; 19 | 20 | export default withStyles(typographyStyle)(Success); 21 | -------------------------------------------------------------------------------- /src/web/src/components/Typography/Warning.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { withStyles } from "material-ui"; 4 | 5 | import typographyStyle from "assets/jss/material-dashboard-react/typographyStyle.jsx"; 6 | 7 | function Warning({ ...props }) { 8 | const { classes, children } = props; 9 | return ( 10 |
11 | {children} 12 |
13 | ); 14 | } 15 | 16 | Warning.propTypes = { 17 | classes: PropTypes.object.isRequired 18 | }; 19 | 20 | export default withStyles(typographyStyle)(Warning); 21 | -------------------------------------------------------------------------------- /src/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fullcycle-api", 3 | "description": "api methods to communicate with full cycle controller", 4 | "version": "0.1.0", 5 | "license": "MIT", 6 | "repository": "none", 7 | "scripts": { 8 | "start": "node src/server.js", 9 | "prod": "npm run start", 10 | "dev": "npm run start" 11 | }, 12 | "dependencies": { 13 | "amqp": "^0.2.6", 14 | "amqplib": "^0.5.2", 15 | "body-parser": "^1.18.2", 16 | "eventsource": "^1.0.5", 17 | "express": "^4.16.3", 18 | "passport": "^0.4.0", 19 | "passport-http": "^0.3.0", 20 | "redis": "^2.8.0", 21 | "sse": "0.0.8" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/web/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { createBrowserHistory } from 'history'; 4 | import { Router, Route, Switch } from 'react-router-dom'; 5 | 6 | import 'assets/css/material-dashboard-react.css?v=1.2.0'; 7 | 8 | import indexRoutes from 'routes/index'; 9 | 10 | const hist = createBrowserHistory(); 11 | 12 | ReactDOM.render( 13 | 14 | 15 | { 16 | indexRoutes.map( 17 | (prop, key) => 18 | ) 19 | } 20 | 21 | , 22 | document.getElementById('root'), 23 | ); 24 | -------------------------------------------------------------------------------- /src/xpackage.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fullcycle-react-express", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "client": "cd client && npm run start", 7 | "server": "nodemon server.js", 8 | "prod": "concurrently \"npm run server\" \"npm run client\"", 9 | "dev": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\"" 10 | }, 11 | "dependencies": { 12 | "amqp": "^0.2.6", 13 | "amqplib": "^0.5.2", 14 | "body-parser": "^1.18.2", 15 | "express": "^4.16.3", 16 | "material-ui": "^1.0.0-beta.42", 17 | "redis": "^2.8.0", 18 | "redux-form-material-ui": "^5.0.0-beta.2" 19 | }, 20 | "devDependencies": { 21 | "concurrently": "^3.5.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/web/src/views/Sensors/icons/camera.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/web/src/components/Typography/Quote.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles } from "material-ui"; 3 | import PropTypes from "prop-types"; 4 | 5 | import typographyStyle from "assets/jss/material-dashboard-react/typographyStyle.jsx"; 6 | 7 | function Quote({ ...props }) { 8 | const { classes, text, author } = props; 9 | return ( 10 |
11 |

{text}

12 | {author} 13 |
14 | ); 15 | } 16 | 17 | Quote.propTypes = { 18 | classes: PropTypes.object.isRequired, 19 | text: PropTypes.node, 20 | author: PropTypes.node 21 | }; 22 | 23 | export default withStyles(typographyStyle)(Quote); 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "type": "node", 10 | "request": "launch", 11 | "name": "Launch via NPM", 12 | "runtimeExecutable": "npm", 13 | "runtimeArgs": [ 14 | "run-script", 15 | "debug" 16 | ], 17 | "port": 5000 18 | }, 19 | { 20 | "type": "node", 21 | "request": "launch", 22 | "name": "Launch Program", 23 | "program": "npm run start" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /src/web/src/views/Dashboard/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { withStyles } from "material-ui"; 4 | import MiningCommands from "./MiningCommands.jsx"; 5 | import MinerSummary from "./MinerSummary.jsx"; 6 | import Sensors from "views/Sensors/Sensors.jsx"; 7 | // react plugin for creating charts 8 | //import ChartistGraph from "react-chartist"; 9 | 10 | import dashboardStyle from "assets/jss/material-dashboard-react/dashboardStyle" 11 | 12 | class Dashboard extends React.Component { 13 | 14 | render() { 15 | return ( 16 |
17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | } 24 | 25 | Dashboard.propTypes = { 26 | classes: PropTypes.object.isRequired 27 | }; 28 | 29 | export default withStyles(dashboardStyle)(Dashboard); 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM arm32v7/node:9 2 | #setup for web server that will serve api as well as static bundled react 3 | 4 | RUN apt-get update && \ 5 | apt-get -y install curl && \ 6 | apt-get -y install python build-essential 7 | 8 | RUN npm install serve-static 9 | 10 | #first copy package and install dependencies 11 | WORKDIR /usr/src/fullcyclereact/src/api/ 12 | COPY src/api/package*.json ./ 13 | RUN npm install 14 | 15 | #then copy api source 16 | COPY src/api/. . 17 | 18 | WORKDIR /usr/src/fullcyclereact/src/web/ 19 | COPY src/web/package*.json ./ 20 | RUN npm install 21 | #RUN npm install @material-ui/icons 22 | #RUN npm install --save @fortawesome/react-fontawesome 23 | 24 | #then copy web source and build it, bundled output in build dir 25 | COPY src/web/. . 26 | RUN npm run build 27 | 28 | WORKDIR /usr/src/fullcyclereact/src/api 29 | 30 | EXPOSE 3000 31 | 32 | #serve up express api with static build content 33 | CMD npm run prod 34 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/appStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // App styles 3 | // ############################# 4 | 5 | import { drawerWidth, transition, container } from "assets/jss/material-dashboard-react.jsx"; 6 | 7 | const appStyle = theme => ({ 8 | wrapper: { 9 | position: "relative", 10 | top: "0", 11 | height: "100vh" 12 | }, 13 | mainPanel: { 14 | [theme.breakpoints.up("md")]: { 15 | width: `calc(100% - ${drawerWidth}px)` 16 | }, 17 | overflow: "auto", 18 | position: "relative", 19 | float: "right", 20 | ...transition, 21 | maxHeight: "100%", 22 | width: "100%", 23 | overflowScrolling: 'touch' 24 | }, 25 | content: { 26 | marginTop: "70px", 27 | padding: "30px 15px", 28 | minHeight: "calc(100% - 123px)" 29 | }, 30 | container, 31 | map: { 32 | marginTop: "70px" 33 | } 34 | }); 35 | 36 | export default appStyle; 37 | -------------------------------------------------------------------------------- /src/web/src/components/CustomButtons/IconButton.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles, IconButton } from "material-ui"; 3 | import PropTypes from "prop-types"; 4 | 5 | import iconButtonStyle from "assets/jss/material-dashboard-react/iconButtonStyle"; 6 | 7 | function IconCustomButton({ ...props }) { 8 | const { classes, color, children, customClass, ...rest } = props; 9 | return ( 10 | 18 | {children} 19 | 20 | ); 21 | } 22 | 23 | IconCustomButton.propTypes = { 24 | classes: PropTypes.object.isRequired, 25 | color: PropTypes.oneOf([ 26 | "primary", 27 | "info", 28 | "success", 29 | "warning", 30 | "danger", 31 | "rose", 32 | "white", 33 | "simple" 34 | ]), 35 | customClass: PropTypes.string, 36 | disabled: PropTypes.bool 37 | }; 38 | 39 | export default withStyles(iconButtonStyle)(IconCustomButton); 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 David Foderick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/web/src/variables/general.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // Tasks for TasksCard - see Dashboard view 3 | // ############################# 4 | 5 | var bugs = [ 6 | 'Sign contract for "What are conference organizers afraid of?"', 7 | "Lines From Great Russian Literature? Or E-mails From My Boss?", 8 | "Flooded: One year later, assessing what was lost and what was found when a ravaging rain swept through metro Detroit", 9 | "Create 4 Invisible User Experiences you Never Knew About" 10 | ]; 11 | var website = [ 12 | "Flooded: One year later, assessing what was lost and what was found when a ravaging rain swept through metro Detroit", 13 | 'Sign contract for "What are conference organizers afraid of?"' 14 | ]; 15 | var server = [ 16 | "Lines From Great Russian Literature? Or E-mails From My Boss?", 17 | "Flooded: One year later, assessing what was lost and what was found when a ravaging rain swept through metro Detroit", 18 | 'Sign contract for "What are conference organizers afraid of?"' 19 | ]; 20 | 21 | module.exports = { 22 | // these 3 are used to create the tasks lists in TasksCard - Dashboard view 23 | bugs, 24 | website, 25 | server 26 | }; 27 | -------------------------------------------------------------------------------- /src/api/src/messages.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | //Message envelope for putting messages on the bus 3 | makeMessage: function (ptype, pbody) { 4 | return { 5 | version: "1.1", 6 | sender: "fullcyclereact", 7 | type: ptype, 8 | timestamp: new Date().toISOString(), 9 | body: pbody 10 | }; 11 | }, 12 | 13 | // MinerMessage 14 | makeMinerMessage: function (pminer, pcommand, ppool){ 15 | return { 16 | miner: pminer, 17 | command: pcommand, 18 | minerstats: null, 19 | minerpool: ppool 20 | } 21 | }, 22 | 23 | //MinerCommandMessge 24 | makeCommand: function (pcommand,pparameter){ 25 | return { 26 | command: pcommand, 27 | parameter: pparameter 28 | } 29 | }, 30 | 31 | // ConfigurationMessage 32 | makeConfigurationMessage: function (pbody){ 33 | return { 34 | command: pbody.command, 35 | parameter: pbody.parameter, 36 | id: pbody.id, 37 | entity: pbody.entity, 38 | values: pbody.values 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/web/src/components/CustomButtons/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles, Button } from "material-ui"; 3 | import PropTypes from "prop-types"; 4 | import cx from "classnames"; 5 | 6 | import buttonStyle from "assets/jss/material-dashboard-react/buttonStyle"; 7 | 8 | function RegularButton({ ...props }) { 9 | const { 10 | classes, 11 | color, 12 | round, 13 | children, 14 | fullWidth, 15 | disabled, 16 | ...rest 17 | } = props; 18 | const btnClasses = cx({ 19 | [classes[color]]: color, 20 | [classes.round]: round, 21 | [classes.fullWidth]: fullWidth, 22 | [classes.disabled]: disabled 23 | }); 24 | return ( 25 | 28 | ); 29 | } 30 | 31 | RegularButton.propTypes = { 32 | classes: PropTypes.object.isRequired, 33 | color: PropTypes.oneOf([ 34 | "primary", 35 | "info", 36 | "success", 37 | "warning", 38 | "danger", 39 | "rose", 40 | "white", 41 | "simple", 42 | "transparent" 43 | ]), 44 | round: PropTypes.bool, 45 | fullWidth: PropTypes.bool, 46 | disabled: PropTypes.bool 47 | }; 48 | 49 | export default withStyles(buttonStyle)(RegularButton); 50 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/regularCardStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // RegularCard styles 3 | // ############################# 4 | 5 | import { 6 | card, 7 | cardHeader, 8 | defaultFont, 9 | orangeCardHeader, 10 | greenCardHeader, 11 | redCardHeader, 12 | blueCardHeader, 13 | purpleCardHeader 14 | } from "assets/jss/material-dashboard-react.jsx"; 15 | 16 | const regularCardStyle = { 17 | card, 18 | cardPlain: { 19 | background: "transparent", 20 | boxShadow: "none" 21 | }, 22 | cardHeader: { 23 | ...cardHeader, 24 | ...defaultFont 25 | }, 26 | cardPlainHeader: { 27 | marginLeft: 0, 28 | marginRight: 0 29 | }, 30 | orangeCardHeader, 31 | greenCardHeader, 32 | redCardHeader, 33 | blueCardHeader, 34 | purpleCardHeader, 35 | cardTitle: { 36 | color: "#FFFFFF", 37 | marginTop: "0", 38 | marginBottom: "5px", 39 | ...defaultFont, 40 | fontSize: "1.125em" 41 | }, 42 | cardSubtitle: { 43 | ...defaultFont, 44 | marginBottom: "0", 45 | color: "rgba(255, 255, 255, 0.62)", 46 | margin: "0 0 10px" 47 | }, 48 | cardActions: { 49 | padding: "14px", 50 | display: "block", 51 | height: "auto" 52 | } 53 | }; 54 | 55 | export default regularCardStyle; 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | build/ 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file. Needed for react. 59 | #.env 60 | # misc 61 | .DS_Store 62 | .env.local 63 | .env.development.local 64 | .env.test.local 65 | .env.production.local 66 | 67 | # next.js build output 68 | .next 69 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/footerStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // Footer styles 3 | // ############################# 4 | 5 | import { defaultFont, container, primaryColor } from "assets/jss/material-dashboard-react.jsx"; 6 | 7 | const footerStyle = { 8 | block: { 9 | color: "inherit", 10 | padding: "15px", 11 | textTransform: "uppercase", 12 | borderRadius: "3px", 13 | textDecoration: "none", 14 | position: "relative", 15 | display: "block", 16 | ...defaultFont, 17 | fontWeight: "500", 18 | fontSize: "12px" 19 | }, 20 | left: { 21 | float: "left!important", 22 | display: "block" 23 | }, 24 | right: { 25 | padding: "15px 0", 26 | margin: "0", 27 | fontSize: "14px", 28 | float: "right!important" 29 | }, 30 | footer: { 31 | bottom: "0", 32 | borderTop: "1px solid #e7e7e7", 33 | padding: "15px 0", 34 | ...defaultFont 35 | }, 36 | container, 37 | a: { 38 | color: primaryColor, 39 | textDecoration: "none", 40 | backgroundColor: "transparent" 41 | }, 42 | list: { 43 | marginBottom: "0", 44 | padding: "0", 45 | marginTop: "0" 46 | }, 47 | inlineBlock: { 48 | display: "inline-block", 49 | paddingTop: "0px", 50 | width: "auto" 51 | } 52 | }; 53 | export default footerStyle; 54 | -------------------------------------------------------------------------------- /src/web/src/components/Footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { List, ListItem, withStyles } from "material-ui"; 4 | import { DonateButton } from "components"; 5 | 6 | import footerStyle from "assets/jss/material-dashboard-react/footerStyle"; 7 | 8 | function Footer({ ...props }) { 9 | const { classes } = props; 10 | return ( 11 | 32 | ); 33 | } 34 | 35 | Footer.propTypes = { 36 | classes: PropTypes.object.isRequired 37 | }; 38 | 39 | export default withStyles(footerStyle)(Footer); 40 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/profileCardStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // ProfileCard styles 3 | // ############################# 4 | 5 | import { card, boxShadow, grayColor, defaultFont } from "assets/jss/material-dashboard-react.jsx"; 6 | 7 | const profileCardStyle = { 8 | card: { 9 | marginTop: "30px", 10 | textAlign: "center", 11 | ...card 12 | }, 13 | cardHeader: { 14 | display: "inline-block", 15 | width: "100%", 16 | padding: "0px" 17 | }, 18 | cardAvatar: { 19 | maxWidth: "130px", 20 | maxHeight: "130px", 21 | margin: "-50px auto 0", 22 | borderRadius: "50%", 23 | overflow: "hidden", 24 | ...boxShadow 25 | }, 26 | img: { 27 | width: "100%", 28 | height: "auto", 29 | verticalAlign: "middle", 30 | border: "0" 31 | }, 32 | textAlign: { 33 | textAlign: "center" 34 | }, 35 | cardSubtitle: { 36 | color: grayColor, 37 | ...defaultFont, 38 | fontSize: "1em", 39 | textTransform: "uppercase", 40 | marginTop: "10px", 41 | marginBottom: "10px" 42 | }, 43 | cardTitle: { 44 | ...defaultFont, 45 | fontSize: "1.3em", 46 | marginTop: "10px", 47 | marginBottom: "10px" 48 | }, 49 | cardDescription: { 50 | ...defaultFont, 51 | padding: "15px 20px", 52 | margin: "0 0 10px" 53 | }, 54 | cardActions: { 55 | height: "auto", 56 | display: "inline" 57 | } 58 | }; 59 | 60 | export default profileCardStyle; 61 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/tableStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // Table styles 3 | // ############################# 4 | 5 | import { 6 | warningColor, 7 | primaryColor, 8 | dangerColor, 9 | successColor, 10 | infoColor, 11 | roseColor, 12 | grayColor, 13 | defaultFont 14 | } from "assets/jss/material-dashboard-react.jsx"; 15 | 16 | const tableStyle = theme => ({ 17 | warningTableHeader: { 18 | color: warningColor 19 | }, 20 | primaryTableHeader: { 21 | color: primaryColor 22 | }, 23 | dangerTableHeader: { 24 | color: dangerColor 25 | }, 26 | successTableHeader: { 27 | color: successColor 28 | }, 29 | infoTableHeader: { 30 | color: infoColor 31 | }, 32 | roseTableHeader: { 33 | color: roseColor 34 | }, 35 | grayTableHeader: { 36 | color: grayColor 37 | }, 38 | table: { 39 | marginBottom: "0", 40 | width: "100%", 41 | maxWidth: "100%", 42 | backgroundColor: "transparent", 43 | borderSpacing: "0", 44 | borderCollapse: "collapse" 45 | }, 46 | tableHeadCell: { 47 | color: "inherit", 48 | ...defaultFont, 49 | fontSize: "1em" 50 | }, 51 | tableCell: { 52 | ...defaultFont, 53 | lineHeight: "1.42857143", 54 | padding: "12px 8px", 55 | verticalAlign: "middle" 56 | }, 57 | tableResponsive: { 58 | width: "100%", 59 | marginTop: theme.spacing.unit * 3, 60 | overflowX: "auto" 61 | } 62 | }); 63 | 64 | export default tableStyle; 65 | -------------------------------------------------------------------------------- /src/web/src/views/Notifications/Notifications.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | //import { Grid } from "material-ui"; 3 | //import { AddAlert } from "@material-ui/icons"; 4 | 5 | import { 6 | RegularCard, 7 | //A, 8 | P 9 | //Small, 10 | //Button, 11 | //SnackbarContent, 12 | //Snackbar, 13 | //ItemGrid 14 | } from "components"; 15 | 16 | class Notifications extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | this.state = {alerts: props.alerts}; 20 | } 21 | 22 | state = { 23 | alerts: [] 24 | }; 25 | 26 | componentWillReceiveProps(nextProps){ 27 | if(nextProps.alerts !== this.props.alerts){ 28 | this.setState({alerts: nextProps.alerts}); 29 | } 30 | } 31 | 32 | renderAlert(alert) { 33 | return ( 34 |
  • 35 | style={{"whiteSpace": "pre-line"}} 36 | {alert} 37 |
  • 38 | ); 39 | } 40 | 41 | render() { 42 | const renderedAlerts = this.state.alerts && this.state.alerts.map((a) => this.renderAlert(a)); 43 | if (renderedAlerts) { 44 | //console.log(renderedAlerts.length); 45 | } else { 46 | //console.log("no alerts"); 47 | } 48 | return ( 49 | 53 | Alerts from Full Cycle Mining Controller 54 |

    55 | } 56 | content={ 57 |
    58 |
      59 | {renderedAlerts} 60 |
    61 |
    62 | } 63 | /> 64 | ); 65 | } 66 | } 67 | 68 | export default Notifications; 69 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/typographyStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // Typography styles 3 | // ############################# 4 | 5 | import { 6 | defaultFont, 7 | primaryColor, 8 | infoColor, 9 | successColor, 10 | warningColor, 11 | dangerColor 12 | } from "assets/jss/material-dashboard-react.jsx"; 13 | 14 | const typographyStyle = { 15 | defaultFontStyle: { 16 | ...defaultFont, 17 | fontSize: "14px" 18 | }, 19 | defaultHeaderMargins: { 20 | marginTop: "20px", 21 | marginBottom: "10px" 22 | }, 23 | pStyle: { 24 | margin: "0 0 10px" 25 | }, 26 | quote: { 27 | padding: "10px 20px", 28 | margin: "0 0 20px", 29 | fontSize: "17.5px", 30 | borderLeft: "5px solid #eee" 31 | }, 32 | quoteText: { 33 | margin: "0 0 10px", 34 | fontStyle: "italic" 35 | }, 36 | quoteAuthor: { 37 | display: "block", 38 | fontSize: "80%", 39 | lineHeight: "1.42857143", 40 | color: "#777" 41 | }, 42 | mutedText: { 43 | color: "#777" 44 | }, 45 | primaryText: { 46 | color: primaryColor 47 | }, 48 | infoText: { 49 | color: infoColor 50 | }, 51 | successText: { 52 | color: successColor 53 | }, 54 | warningText: { 55 | color: warningColor 56 | }, 57 | dangerText: { 58 | color: dangerColor 59 | }, 60 | smallText: { 61 | fontSize: "65%", 62 | fontWeight: "400", 63 | lineHeight: "1", 64 | color: "#777" 65 | }, 66 | aStyle: { 67 | textDecoration: "none", 68 | backgroundColor: "transparent", 69 | "&,&:hover": { 70 | color: "#FFFFFF" 71 | } 72 | } 73 | }; 74 | 75 | export default typographyStyle; 76 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/customInputStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // CustomInput styles 3 | // ############################# 4 | 5 | import { 6 | primaryColor, 7 | dangerColor, 8 | successColor, 9 | defaultFont 10 | } from "assets/jss/material-dashboard-react.jsx"; 11 | 12 | const customInputStyle = { 13 | disabled: { 14 | "&:before": { 15 | backgroundColor: "transparent !important" 16 | } 17 | }, 18 | underline: { 19 | "&:hover:not($disabled):before,&:before": { 20 | backgroundColor: "#D2D2D2", 21 | height: "1px !important" 22 | }, 23 | "&:after": { 24 | backgroundColor: primaryColor 25 | } 26 | }, 27 | underlineError: { 28 | "&:after": { 29 | backgroundColor: dangerColor 30 | } 31 | }, 32 | underlineSuccess: { 33 | "&:after": { 34 | backgroundColor: successColor 35 | } 36 | }, 37 | labelRoot: { 38 | ...defaultFont, 39 | color: "#AAAAAA", 40 | fontWeight: "400", 41 | fontSize: "14px", 42 | lineHeight: "1.42857" 43 | }, 44 | labelRootError: { 45 | color: dangerColor 46 | }, 47 | labelRootSuccess: { 48 | color: successColor 49 | }, 50 | feedback: { 51 | position: "absolute", 52 | top: "18px", 53 | right: "0", 54 | zIndex: "2", 55 | display: "block", 56 | width: "24px", 57 | height: "24px", 58 | textAlign: "center", 59 | pointerEvents: "none" 60 | }, 61 | marginTop: { 62 | marginTop: "16px" 63 | }, 64 | formControl: { 65 | paddingBottom: "10px", 66 | margin: "27px 0 0 0", 67 | position: "relative" 68 | } 69 | }; 70 | 71 | export default customInputStyle; 72 | -------------------------------------------------------------------------------- /src/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fullcycle-web", 3 | "description": "full cycle mining controller web", 4 | "version": "0.1.0", 5 | "license": "MIT", 6 | "private": false, 7 | "proxy": "http://127.0.0.1:5000/", 8 | "dependencies": { 9 | "@babel/runtime": "^7.0.0-beta.46", 10 | "@material-ui/icons": "^1.0.0-beta.42", 11 | "@moneybutton/react-money-button": "^0.1.1", 12 | "chartist": "0.10.1", 13 | "classnames": "^2.2.5", 14 | "history": "^4.7.2", 15 | "material-ui": "1.0.0-beta.41", 16 | "npm-run-all": "4.1.2", 17 | "passport": "^0.4.0", 18 | "passport-local": "^1.0.0", 19 | "perfect-scrollbar": "1.3.0", 20 | "react": "^16.3.2", 21 | "react-chartist": "0.13.1", 22 | "react-dom": "^16.3.2", 23 | "react-router-dom": "4.2.2", 24 | "react-scripts": "1.1.4", 25 | "react-swipeable-views": "0.12.12", 26 | "redux-form-material-ui": "^5.0.0-beta.2", 27 | "withstyles": "^1.0.3" 28 | }, 29 | "scripts": { 30 | "start": "react-scripts start", 31 | "build": "react-scripts build", 32 | "test": "react-scripts test --env=jsdom", 33 | "eject": "react-scripts eject" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/dfoderick/fullcyclereact.git" 38 | }, 39 | "keywords": [], 40 | "author": "Dave Foderick ", 41 | "bugs": { 42 | "url": "https://github.com/dfoderick/fullcyclereact/issues" 43 | }, 44 | "devDependencies": { 45 | "eslint": "^5.6.0", 46 | "eslint-config-airbnb": "^17.1.0", 47 | "eslint-plugin-import": "^2.14.0", 48 | "eslint-plugin-jsx-a11y": "^6.1.1", 49 | "eslint-plugin-react": "^7.11.1" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/web/src/components/Snackbar/SnackbarContent.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles, SnackbarContent as Snack, IconButton } from "material-ui"; 3 | import { Close } from "@material-ui/icons"; 4 | import PropTypes from "prop-types"; 5 | import cx from "classnames"; 6 | 7 | import snackbarContentStyle from "assets/jss/material-dashboard-react/snackbarContentStyle.jsx"; 8 | 9 | function SnackbarContent({ ...props }) { 10 | const { classes, message, color, close, icon } = props; 11 | var action = []; 12 | const messageClasses = cx({ 13 | [classes.iconMessage]: icon !== undefined 14 | }); 15 | if (close !== undefined) { 16 | action = [ 17 | 23 | 24 | 25 | ]; 26 | } 27 | return ( 28 | 31 | {icon !== undefined ? : null} 32 | {message} 33 | 34 | } 35 | classes={{ 36 | root: classes.root + " " + classes[color], 37 | message: classes.message 38 | }} 39 | action={action} 40 | /> 41 | ); 42 | } 43 | 44 | SnackbarContent.propTypes = { 45 | classes: PropTypes.object.isRequired, 46 | message: PropTypes.node.isRequired, 47 | color: PropTypes.oneOf(["info", "success", "warning", "danger", "primary"]), 48 | close: PropTypes.bool, 49 | icon: PropTypes.func 50 | }; 51 | 52 | export default withStyles(snackbarContentStyle)(SnackbarContent); 53 | -------------------------------------------------------------------------------- /src/web/src/views/Sensors/icons/temperature.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/web/src/components/Cards/ProfileCard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | withStyles, 4 | Card, 5 | CardHeader, 6 | CardContent, 7 | CardActions, 8 | Typography 9 | } from "material-ui"; 10 | import PropTypes from "prop-types"; 11 | 12 | import profileCardStyle from "assets/jss/material-dashboard-react/profileCardStyle"; 13 | 14 | function ProfileCard({ ...props }) { 15 | const { classes, subtitle, title, description, footer, avatar } = props; 16 | return ( 17 | 18 | } 24 | /> 25 | 26 | {subtitle !== undefined ? ( 27 | 28 | {subtitle} 29 | 30 | ) : null} 31 | {title !== undefined ? ( 32 | 33 | {title} 34 | 35 | ) : null} 36 | {description !== undefined ? ( 37 | 38 | {description} 39 | 40 | ) : null} 41 | 42 | 43 | {footer} 44 | 45 | 46 | ); 47 | } 48 | 49 | ProfileCard.propTypes = { 50 | classes: PropTypes.object.isRequired, 51 | title: PropTypes.node, 52 | subtitle: PropTypes.node, 53 | description: PropTypes.node, 54 | footer: PropTypes.node, 55 | avatar: PropTypes.string 56 | }; 57 | 58 | export default withStyles(profileCardStyle)(ProfileCard); 59 | -------------------------------------------------------------------------------- /src/web/src/components/Cards/RegularCard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | withStyles, 4 | Card, 5 | CardContent, 6 | CardHeader, 7 | CardActions 8 | } from "material-ui"; 9 | import PropTypes from "prop-types"; 10 | import cx from "classnames"; 11 | 12 | import regularCardStyle from "assets/jss/material-dashboard-react/regularCardStyle"; 13 | 14 | function RegularCard({ ...props }) { 15 | const { 16 | classes, 17 | headerColor, 18 | plainCard, 19 | cardTitle, 20 | cardSubtitle, 21 | content, 22 | footer 23 | } = props; 24 | const plainCardClasses = cx({ 25 | [" " + classes.cardPlain]: plainCard 26 | }); 27 | const cardPlainHeaderClasses = cx({ 28 | [" " + classes.cardPlainHeader]: plainCard 29 | }); 30 | return ( 31 | 32 | 45 | {content} 46 | {footer !== undefined ? ( 47 | {footer} 48 | ) : null} 49 | 50 | ); 51 | } 52 | 53 | RegularCard.defaultProps = { 54 | headerColor: "blue" 55 | }; 56 | 57 | RegularCard.propTypes = { 58 | plainCard: PropTypes.bool, 59 | classes: PropTypes.object.isRequired, 60 | headerColor: PropTypes.oneOf(["orange", "green", "red", "blue", "purple"]), 61 | cardTitle: PropTypes.node, 62 | cardSubtitle: PropTypes.node, 63 | content: PropTypes.node, 64 | footer: PropTypes.node 65 | }; 66 | 67 | export default withStyles(regularCardStyle)(RegularCard); 68 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/headerStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // Header styles 3 | // ############################# 4 | 5 | import { 6 | container, 7 | defaultFont, 8 | primaryColor, 9 | defaultBoxShadow, 10 | infoColor, 11 | successColor, 12 | warningColor, 13 | dangerColor 14 | } from "assets/jss/material-dashboard-react.jsx"; 15 | 16 | const headerStyle = theme => ({ 17 | appBar: { 18 | backgroundColor: "transparent", 19 | boxShadow: "none", 20 | borderBottom: "0", 21 | marginBottom: "0", 22 | position: "absolute", 23 | width: "100%", 24 | paddingTop: "10px", 25 | zIndex: "1029", 26 | color: "#555555", 27 | border: "0", 28 | borderRadius: "3px", 29 | padding: "10px 0", 30 | transition: "all 150ms ease 0s", 31 | minHeight: "50px", 32 | display: "block" 33 | }, 34 | container: { 35 | ...container, 36 | minHeight: "50px", 37 | }, 38 | flex: { 39 | flex: 1 40 | }, 41 | title: { 42 | ...defaultFont, 43 | lineHeight: "30px", 44 | fontSize: "18px", 45 | borderRadius: "3px", 46 | textTransform: "none", 47 | color: "inherit", 48 | "&:hover,&:focus": { 49 | background: "transparent" 50 | } 51 | }, 52 | appResponsive: { 53 | top: "8px" 54 | }, 55 | primary: { 56 | backgroundColor: primaryColor, 57 | color: "#FFFFFF", 58 | ...defaultBoxShadow 59 | }, 60 | info: { 61 | backgroundColor: infoColor, 62 | color: "#FFFFFF", 63 | ...defaultBoxShadow 64 | }, 65 | success: { 66 | backgroundColor: successColor, 67 | color: "#FFFFFF", 68 | ...defaultBoxShadow 69 | }, 70 | warning: { 71 | backgroundColor: warningColor, 72 | color: "#FFFFFF", 73 | ...defaultBoxShadow 74 | }, 75 | danger: { 76 | backgroundColor: dangerColor, 77 | color: "#FFFFFF", 78 | ...defaultBoxShadow 79 | } 80 | }); 81 | 82 | export default headerStyle; 83 | -------------------------------------------------------------------------------- /src/web/src/components/Header/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Menu } from "@material-ui/icons"; 4 | import { 5 | withStyles, 6 | AppBar, 7 | Toolbar, 8 | IconButton, 9 | Hidden, 10 | Button 11 | } from "material-ui"; 12 | import cx from "classnames"; 13 | 14 | import headerStyle from "assets/jss/material-dashboard-react/headerStyle.jsx"; 15 | 16 | import HeaderLinks from "./HeaderLinks"; 17 | 18 | function Header({ ...props }) { 19 | function makeBrand() { 20 | var name; 21 | props.routes.map((prop, key) => { 22 | if (prop.path === props.location.pathname) { 23 | name = prop.navbarName; 24 | } 25 | return null; 26 | }); 27 | return name; 28 | } 29 | const { classes, color } = props; 30 | const appBarClasses = cx({ 31 | [" " + classes[color]]: color 32 | }); 33 | return ( 34 | 35 | 36 |
    37 | {/* Here we create navbar brand, based on route name */} 38 | 41 |
    42 | 43 | 44 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | ); 58 | } 59 | 60 | Header.propTypes = { 61 | classes: PropTypes.object.isRequired, 62 | color: PropTypes.oneOf(["primary", "info", "success", "warning", "danger"]) 63 | }; 64 | 65 | export default withStyles(headerStyle)(Header); 66 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/snackbarContentStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // SnackbarContent styles 3 | // ############################# 4 | 5 | import { 6 | defaultFont, 7 | primaryBoxShadow, 8 | infoBoxShadow, 9 | successBoxShadow, 10 | warningBoxShadow, 11 | dangerBoxShadow 12 | } from "assets/jss/material-dashboard-react.jsx"; 13 | 14 | const snackbarContentStyle = { 15 | root: { 16 | ...defaultFont, 17 | position: "relative", 18 | padding: "20px 15px", 19 | lineHeight: "20px", 20 | marginBottom: "20px", 21 | fontSize: "14px", 22 | backgroundColor: "white", 23 | color: "#555555", 24 | borderRadius: "3px", 25 | boxShadow: 26 | "0 12px 20px -10px rgba(255, 255, 255, 0.28), 0 4px 20px 0px rgba(0, 0, 0, 0.12), 0 7px 8px -5px rgba(255, 255, 255, 0.2)" 27 | }, 28 | info: { 29 | backgroundColor: "#00d3ee", 30 | color: "#ffffff", 31 | ...infoBoxShadow 32 | }, 33 | success: { 34 | backgroundColor: "#5cb860", 35 | color: "#ffffff", 36 | ...successBoxShadow 37 | }, 38 | warning: { 39 | backgroundColor: "#ffa21a", 40 | color: "#ffffff", 41 | ...warningBoxShadow 42 | }, 43 | danger: { 44 | backgroundColor: "#f55a4e", 45 | color: "#ffffff", 46 | ...dangerBoxShadow 47 | }, 48 | primary: { 49 | backgroundColor: "#af2cc5", 50 | color: "#ffffff", 51 | ...primaryBoxShadow 52 | }, 53 | message: { 54 | padding: "0", 55 | display: "block", 56 | maxWidth: "89%" 57 | }, 58 | close: { 59 | width: "14px", 60 | height: "14px" 61 | }, 62 | iconButton: { 63 | width: "24px", 64 | height: "24px" 65 | }, 66 | icon: { 67 | display: "block", 68 | left: "15px", 69 | position: "absolute", 70 | top: "50%", 71 | marginTop: "-15px", 72 | width: "30px", 73 | height: "30px" 74 | }, 75 | iconMessage: { 76 | paddingLeft: "65px", 77 | display: "block" 78 | } 79 | }; 80 | 81 | export default snackbarContentStyle; 82 | -------------------------------------------------------------------------------- /src/web/src/routes/dashboard.jsx: -------------------------------------------------------------------------------- 1 | import DashboardPage from "views/Dashboard/Dashboard.jsx"; 2 | import Miners from "views/Miners/Miners.jsx"; 3 | import Sensors from "views/Sensors/Sensors.jsx"; 4 | import Pools from "views/Pools/Pools.jsx"; 5 | import About from "views/About/About.jsx"; 6 | 7 | // import UserProfile from "views/UserProfile/UserProfile.jsx"; 8 | // import TableList from "views/TableList/TableList.jsx"; 9 | import NotificationsPage from "views/Notifications/Notifications.jsx"; 10 | 11 | import { 12 | Dashboard, 13 | Computer, 14 | SettingsEthernet, 15 | SettingsInputAntenna, 16 | Info, 17 | Notifications 18 | } from "@material-ui/icons"; 19 | 20 | const dashboardRoutes = [ 21 | { 22 | path: "/dashboard", 23 | sidebarName: "Dashboard", 24 | navbarName: "Full Cycle Dashboard", 25 | icon: Dashboard, 26 | component: DashboardPage 27 | }, 28 | { 29 | path: "/miners", 30 | sidebarName: "Miners", 31 | navbarName: "Miners", 32 | icon: Computer, 33 | component: Miners 34 | }, 35 | { 36 | path: "/sensors", 37 | sidebarName: "Sensors", 38 | navbarName: "Sensors", 39 | icon: SettingsInputAntenna, 40 | component: Sensors 41 | }, 42 | { 43 | path: "/pools", 44 | sidebarName: "Pools", 45 | navbarName: "Pools", 46 | icon: SettingsEthernet, 47 | component: Pools 48 | }, 49 | 50 | // { 51 | // path: "/user", 52 | // sidebarName: "User Profile", 53 | // navbarName: "Profile", 54 | // icon: Person, 55 | // component: UserProfile 56 | // }, 57 | 58 | { 59 | path: "/notifications", 60 | sidebarName: "Notifications", 61 | navbarName: "Notifications", 62 | icon: Notifications, 63 | component: NotificationsPage 64 | }, 65 | { 66 | path: "/about", 67 | sidebarName: "About", 68 | navbarName: "About", 69 | icon: Info, 70 | component: About 71 | }, 72 | { redirect: true, path: "/", to: "/dashboard", navbarName: "Redirect" } 73 | ]; 74 | 75 | export default dashboardRoutes; 76 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/tasksCardStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // TasksCard styles 3 | // ############################# 4 | 5 | import { 6 | card, 7 | cardHeader, 8 | defaultFont, 9 | primaryBoxShadow 10 | } from "assets/jss/material-dashboard-react.jsx"; 11 | 12 | const tasksCardStyle = theme => ({ 13 | card, 14 | cardHeader: { 15 | flex: "none", 16 | ...cardHeader, 17 | ...defaultFont, 18 | background: "linear-gradient(60deg, #ab47bc, #8e24aa)", 19 | ...primaryBoxShadow 20 | }, 21 | cardTitle: { 22 | ...defaultFont, 23 | float: "left", 24 | fontWeight: "500", 25 | padding: "10px 10px 10px 0", 26 | lineHeight: "24px", 27 | fontSize: "14px", 28 | color: "#FFFFFF" 29 | }, 30 | tabWrapper: { 31 | width: "auto", 32 | display: "inline-flex", 33 | alignItems: "inherit", 34 | flexDirection: "row", 35 | justifyContent: "center", 36 | [theme.breakpoints.down("sm")]: { 37 | display: "flex" 38 | } 39 | }, 40 | tabIcon: { 41 | float: "left", 42 | [theme.breakpoints.down("sm")]: { 43 | marginTop: "-2px" 44 | } 45 | }, 46 | displayNone: { 47 | display: "none" 48 | }, 49 | labelIcon: { 50 | height: "44px", 51 | width: "110px", 52 | minWidth: "72px", 53 | paddingLeft: "14px", 54 | borderRadius: "3px" 55 | }, 56 | tabsContainer: { 57 | marginTop: "4px", 58 | color: "#FFFFFF", 59 | [theme.breakpoints.down("sm")]: { 60 | display: "grid" 61 | } 62 | }, 63 | tabs: { 64 | width: "110px", 65 | minWidth: "70px", 66 | paddingLeft: "12px" 67 | }, 68 | cardHeaderContent: { 69 | flex: "none" 70 | }, 71 | label: { 72 | lineHeight: "19px", 73 | textTransform: "uppercase", 74 | fontSize: "12px", 75 | fontWeight: "500", 76 | marginLeft: "-10px" 77 | }, 78 | textColorInheritSelected: { 79 | backgroundColor: "rgba(255, 255, 255, 0.2)", 80 | transition: "background-color .4s" 81 | } 82 | }); 83 | 84 | export default tasksCardStyle; 85 | -------------------------------------------------------------------------------- /src/web/src/components/Snackbar/Snackbar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles, Snackbar as Snack, IconButton } from "material-ui"; 3 | import { Close } from "@material-ui/icons"; 4 | import PropTypes from "prop-types"; 5 | import cx from "classnames"; 6 | 7 | import snackbarContentStyle from "assets/jss/material-dashboard-react/snackbarContentStyle.jsx"; 8 | 9 | function Snackbar({ ...props }) { 10 | const { classes, message, color, close, icon, place, open } = props; 11 | var action = []; 12 | const messageClasses = cx({ 13 | [classes.iconMessage]: icon !== undefined 14 | }); 15 | if (close !== undefined) { 16 | action = [ 17 | props.closeNotification()} 23 | > 24 | 25 | 26 | ]; 27 | } 28 | return ( 29 | 40 | {icon !== undefined ? : null} 41 | {message} 42 | 43 | } 44 | action={action} 45 | SnackbarContentProps={{ 46 | classes: { 47 | root: classes.root + " " + classes[color], 48 | message: classes.message 49 | } 50 | }} 51 | /> 52 | ); 53 | } 54 | 55 | Snackbar.propTypes = { 56 | classes: PropTypes.object.isRequired, 57 | message: PropTypes.node.isRequired, 58 | color: PropTypes.oneOf(["info", "success", "warning", "danger", "primary"]), 59 | close: PropTypes.bool, 60 | icon: PropTypes.func, 61 | place: PropTypes.oneOf(["tl", "tr", "tc", "br", "bl", "bc"]), 62 | open: PropTypes.bool 63 | }; 64 | 65 | export default withStyles(snackbarContentStyle)(Snackbar); 66 | -------------------------------------------------------------------------------- /src/web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 25 | Full Cycle Mining Web Site 26 | 27 | 28 | 31 |
    32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/chartCardStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // ChartCard styles 3 | // ############################# 4 | 5 | import { 6 | card, 7 | cardHeader, 8 | defaultFont, 9 | orangeCardHeader, 10 | greenCardHeader, 11 | redCardHeader, 12 | blueCardHeader, 13 | purpleCardHeader, 14 | cardActions, 15 | grayColor, 16 | warningColor, 17 | dangerColor, 18 | successColor, 19 | infoColor, 20 | primaryColor, 21 | roseColor 22 | } from "assets/jss/material-dashboard-react.jsx"; 23 | 24 | const chartCardStyle = { 25 | card, 26 | cardHeader: { 27 | ...cardHeader, 28 | padding: "0", 29 | minHeight: "160px", 30 | ...defaultFont 31 | }, 32 | orangeCardHeader, 33 | greenCardHeader, 34 | redCardHeader, 35 | blueCardHeader, 36 | purpleCardHeader, 37 | cardContent: { 38 | padding: "15px 20px" 39 | }, 40 | cardTitle: { 41 | marginTop: "0", 42 | marginBottom: "5px", 43 | ...defaultFont, 44 | fontSize: "1.175em" 45 | }, 46 | cardCategory: { 47 | marginBottom: "0", 48 | color: grayColor, 49 | ...defaultFont, 50 | fontSize: "0.9em" 51 | }, 52 | cardActions: { 53 | ...cardActions, 54 | padding: "0!important" 55 | }, 56 | cardStats: { 57 | lineHeight: "22px", 58 | color: grayColor, 59 | fontSize: "12px", 60 | display: "inline-block", 61 | margin: "0!important" 62 | }, 63 | cardStatsIcon: { 64 | position: "relative", 65 | top: "4px", 66 | width: "16px", 67 | height: "16px" 68 | }, 69 | warningCardStatsIcon: { 70 | color: warningColor 71 | }, 72 | primaryCardStatsIcon: { 73 | color: primaryColor 74 | }, 75 | dangerCardStatsIcon: { 76 | color: dangerColor 77 | }, 78 | successCardStatsIcon: { 79 | color: successColor 80 | }, 81 | infoCardStatsIcon: { 82 | color: infoColor 83 | }, 84 | roseCardStatsIcon: { 85 | color: roseColor 86 | }, 87 | grayCardStatsIcon: { 88 | color: grayColor 89 | }, 90 | cardStatsLink: { 91 | color: primaryColor, 92 | textDecoration: "none", 93 | ...defaultFont 94 | } 95 | }; 96 | 97 | export default chartCardStyle; 98 | -------------------------------------------------------------------------------- /src/web/src/views/Pools/Pools.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid } from "material-ui"; 3 | import { RegularCard, ItemGrid } from "components"; 4 | 5 | import PoolsTable from "./PoolsTable"; 6 | 7 | class Pools extends React.Component { 8 | state = { 9 | knownpools: "", 10 | }; 11 | 12 | componentDidMount() { 13 | this.callApiGetPools() 14 | .then(res => this.setState({ knownpools: res.knownpools })) 15 | .catch(err => console.log(err)); 16 | } 17 | 18 | callApiGetPools = async () => { 19 | const response = await fetch("/api/knownpools"); 20 | const body = await response.json(); 21 | if (response.status !== 200) throw Error(body.message); 22 | return body; 23 | }; 24 | 25 | render() { 26 | const jpools = JSON.parse(JSON.stringify(this.state.knownpools)); 27 | const arrPools = []; 28 | if (jpools != null){ 29 | Object.keys(jpools).forEach(function(key) { 30 | arrPools.push(JSON.parse(jpools[key], function (key, value) { 31 | return (value == null) ? "" : value; 32 | })); 33 | }); 34 | } 35 | 36 | const poolsNamed = arrPools.filter(pool => pool.named_pool); 37 | const poolsAvailable = arrPools.filter(pool => !pool.named_pool); 38 | 39 | return ( 40 | 41 | 42 | 47 | } 48 | /> 49 | 50 | 51 | 56 | } 57 | /> 58 | 59 | 60 | ); 61 | } 62 | } 63 | 64 | export default Pools; 65 | -------------------------------------------------------------------------------- /src/web/src/components/Table/Table.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | withStyles, 4 | Table, 5 | TableHead, 6 | TableRow, 7 | TableBody, 8 | TableCell 9 | } from "material-ui"; 10 | 11 | import PropTypes from "prop-types"; 12 | 13 | import tableStyle from "assets/jss/material-dashboard-react/tableStyle"; 14 | 15 | function CustomTable({ ...props }) { 16 | const { classes, tableHead, tableData, tableHeaderColor } = props; 17 | return ( 18 |
    19 | 20 | {tableHead !== undefined ? ( 21 | 22 | 23 | {tableHead.map((prop, key) => { 24 | return ( 25 | 29 | {prop} 30 | 31 | ); 32 | })} 33 | 34 | 35 | ) : null} 36 | 37 | {tableData.map((prop, key) => { 38 | return ( 39 | 40 | {prop.map((prop, key) => { 41 | return ( 42 | 43 | {prop} 44 | 45 | ); 46 | })} 47 | 48 | ); 49 | })} 50 | 51 |
    52 |
    53 | ); 54 | } 55 | 56 | CustomTable.defaultProps = { 57 | tableHeaderColor: "gray" 58 | }; 59 | 60 | CustomTable.propTypes = { 61 | classes: PropTypes.object.isRequired, 62 | tableHeaderColor: PropTypes.oneOf([ 63 | "warning", 64 | "primary", 65 | "danger", 66 | "success", 67 | "info", 68 | "rose", 69 | "gray" 70 | ]), 71 | tableHead: PropTypes.arrayOf(PropTypes.string), 72 | tableData: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)) 73 | }; 74 | 75 | export default withStyles(tableStyle)(CustomTable); 76 | -------------------------------------------------------------------------------- /src/web/src/views/Icons/Icons.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { withStyles, Grid, Hidden } from "material-ui"; 4 | 5 | import { RegularCard, P, A, ItemGrid } from "components"; 6 | 7 | import iconsStyle from "assets/jss/material-dashboard-react/iconsStyle"; 8 | 9 | function Icons({ ...props }) { 10 | return ( 11 | 12 | 13 | 18 | Handcrafted by our friends from{" "} 19 | 24 | Google 25 | 26 |

    27 | } 28 | content={ 29 |
    30 | 31 | 38 | 39 | 40 | 41 |
    42 | The icons are visible on Desktop mode inside an iframe. 43 | Since the iframe is not working on Mobile and Tablets please 44 | visit the icons on their original page on Google. Check the 45 | 50 | Material Icons 51 | 52 |
    53 |
    54 |
    55 |
    56 | } 57 | /> 58 |
    59 |
    60 | ); 61 | } 62 | 63 | Icons.propTypes = { 64 | classes: PropTypes.object.isRequired 65 | }; 66 | 67 | export default withStyles(iconsStyle)(Icons); 68 | -------------------------------------------------------------------------------- /src/web/src/components/CustomInput/CustomInput.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles, FormControl, InputLabel, Input } from "material-ui"; 3 | import { Clear, Check } from "@material-ui/icons"; 4 | import PropTypes from "prop-types"; 5 | import cx from "classnames"; 6 | 7 | import customInputStyle from "assets/jss/material-dashboard-react/customInputStyle"; 8 | 9 | function CustomInput({ ...props }) { 10 | const { 11 | classes, 12 | formControlProps, 13 | labelText, 14 | id, 15 | labelProps, 16 | inputProps, 17 | error, 18 | success 19 | } = props; 20 | 21 | const labelClasses = cx({ 22 | [" " + classes.labelRootError]: error, 23 | [" " + classes.labelRootSuccess]: success && !error 24 | }); 25 | const underlineClasses = cx({ 26 | [classes.underlineError]: error, 27 | [classes.underlineSuccess]: success && !error, 28 | [classes.underline]: true 29 | }); 30 | const marginTop = cx({ 31 | [classes.marginTop]: labelText === undefined 32 | }); 33 | return ( 34 | 38 | {labelText !== undefined ? ( 39 | 44 | {labelText} 45 | 46 | ) : null} 47 | 56 | {error ? ( 57 | 58 | ) : success ? ( 59 | 60 | ) : null} 61 | 62 | ); 63 | } 64 | 65 | CustomInput.propTypes = { 66 | classes: PropTypes.object.isRequired, 67 | labelText: PropTypes.node, 68 | labelProps: PropTypes.object, 69 | id: PropTypes.string, 70 | inputProps: PropTypes.object, 71 | formControlProps: PropTypes.object, 72 | error: PropTypes.bool, 73 | success: PropTypes.bool 74 | }; 75 | 76 | export default withStyles(customInputStyle)(CustomInput); 77 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/tasksStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // Tasks styles 3 | // ############################# 4 | 5 | import { defaultFont, primaryColor, dangerColor } from "assets/jss/material-dashboard-react.jsx"; 6 | 7 | const tasksStyle = { 8 | table: { 9 | marginBottom: "0", 10 | overflow: "visible", 11 | }, 12 | tableRow: { 13 | position: "relative", 14 | borderBottom: "1px solid #dddddd" 15 | }, 16 | tableActions: { 17 | display: "flex", 18 | border: "none", 19 | padding: "12px 8px !important", 20 | verticalAlign: "middle" 21 | }, 22 | tableCell: { 23 | ...defaultFont, 24 | padding: "8px", 25 | verticalAlign: "middle", 26 | border: "none", 27 | lineHeight: "1.42857143", 28 | fontSize: "14px" 29 | }, 30 | tableActionButton: { 31 | width: "27px", 32 | height: "27px" 33 | }, 34 | tableActionButtonIcon: { 35 | width: "17px", 36 | height: "17px" 37 | }, 38 | edit: { 39 | backgroundColor: "transparent", 40 | color: primaryColor, 41 | boxShadow: "none" 42 | }, 43 | close: { 44 | backgroundColor: "transparent", 45 | color: dangerColor, 46 | boxShadow: "none" 47 | }, 48 | checked: { 49 | color: primaryColor 50 | }, 51 | checkedIcon: { 52 | width: "20px", 53 | height: "20px", 54 | border: "1px solid rgba(0, 0, 0, .54)", 55 | borderRadius: "3px" 56 | }, 57 | uncheckedIcon: { 58 | width: "0px", 59 | height: "0px", 60 | padding: "10px", 61 | border: "1px solid rgba(0, 0, 0, .54)", 62 | borderRadius: "3px" 63 | }, 64 | tooltip: { 65 | padding: "10px 15px", 66 | minWidth: "130px", 67 | color: "#555555", 68 | lineHeight: "1.7em", 69 | background: "#FFFFFF", 70 | border: "none", 71 | borderRadius: "3px", 72 | boxShadow: 73 | "0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2)", 74 | maxWidth: "200px", 75 | textAlign: "center", 76 | fontFamily: '"Helvetica Neue",Helvetica,Arial,sans-serif', 77 | fontSize: "12px", 78 | fontStyle: "normal", 79 | fontWeight: "400", 80 | textShadow: "none", 81 | textTransform: "none", 82 | letterSpacing: "normal", 83 | wordBreak: "normal", 84 | wordSpacing: "normal", 85 | wordWrap: "normal", 86 | whiteSpace: "normal", 87 | lineBreak: "auto" 88 | } 89 | }; 90 | export default tasksStyle; 91 | -------------------------------------------------------------------------------- /src/web/src/views/TableList/TableList.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid } from "material-ui"; 3 | 4 | import { RegularCard, Table, ItemGrid } from "components"; 5 | 6 | function TableList({ ...props }) { 7 | return ( 8 | 9 | 10 | 26 | } 27 | /> 28 | 29 | 30 | 59 | } 60 | /> 61 | 62 | 63 | ); 64 | } 65 | 66 | export default TableList; 67 | -------------------------------------------------------------------------------- /src/web/src/components/Cards/ChartCard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | withStyles, 4 | Card, 5 | CardContent, 6 | CardHeader, 7 | CardActions, 8 | Typography 9 | } from "material-ui"; 10 | import PropTypes from "prop-types"; 11 | 12 | import chartCardStyle from "assets/jss/material-dashboard-react/chartCardStyle"; 13 | 14 | function ChartCard({ ...props }) { 15 | const { 16 | classes, 17 | chartColor, 18 | statIconColor, 19 | chart, 20 | title, 21 | text, 22 | statLink, 23 | statText 24 | } = props; 25 | return ( 26 | 27 | 33 | 34 | 35 | {title} 36 | 37 | 38 | {text} 39 | 40 | 41 | 42 |
    43 | {" "} 50 | {statLink !== undefined ? ( 51 | 52 | {statLink.text} 53 | 54 | ) : statText !== undefined ? ( 55 | statText 56 | ) : null} 57 |
    58 |
    59 |
    60 | ); 61 | } 62 | 63 | ChartCard.defaultProps = { 64 | statIconColor: "gray", 65 | chartColor: "purple" 66 | }; 67 | 68 | ChartCard.propTypes = { 69 | classes: PropTypes.object.isRequired, 70 | chart: PropTypes.object.isRequired, 71 | title: PropTypes.node, 72 | text: PropTypes.node, 73 | statIcon: PropTypes.func.isRequired, 74 | statIconColor: PropTypes.oneOf([ 75 | "warning", 76 | "primary", 77 | "danger", 78 | "success", 79 | "info", 80 | "rose", 81 | "gray" 82 | ]), 83 | chartColor: PropTypes.oneOf(["orange", "green", "red", "blue", "purple"]), 84 | statLink: PropTypes.object, 85 | statText: PropTypes.node 86 | }; 87 | 88 | export default withStyles(chartCardStyle)(ChartCard); 89 | -------------------------------------------------------------------------------- /src/web/src/views/About/About.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid } from "material-ui"; 3 | import { RegularCard, ItemGrid } from "components"; 4 | import { DonateButton } from "components"; 5 | 6 | class About extends React.Component { 7 | render() { 8 | return ( 9 |
    10 | 11 | 12 | 17 |
    18 | Source code is at GitHub 19 |
    20 |
    21 | Project page GitHub 22 |
    23 |
    24 | Wiki 25 |
    26 |
    27 | Author David Foderick dfoderick@gmail.com 28 |
    29 |
    30 | 31 |
    32 |
    33 | } 34 | /> 35 | 36 | 37 | 38 | 39 | 44 |
    45 | Dashboard theme provided by Creative Tim 46 |
    47 | 48 | © {1900 + new Date().getYear()}{" "} 49 | 50 | Creative Tim 51 | , made with love for a better web 52 | 53 | 54 | } 55 | /> 56 |
    57 |
    58 | 59 | ); 60 | } 61 | } 62 | 63 | export default About; 64 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/statsCardStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // StatsCard styles 3 | // ############################# 4 | 5 | import { 6 | card, 7 | cardHeader, 8 | defaultFont, 9 | orangeCardHeader, 10 | greenCardHeader, 11 | redCardHeader, 12 | blueCardHeader, 13 | purpleCardHeader, 14 | cardActions, 15 | grayColor, 16 | warningColor, 17 | dangerColor, 18 | successColor, 19 | infoColor, 20 | primaryColor, 21 | roseColor 22 | } from "assets/jss/material-dashboard-react.jsx"; 23 | 24 | const statsCardStyle = { 25 | card, 26 | cardHeader: { 27 | ...cardHeader, 28 | float: "left", 29 | textAlign: "center" 30 | }, 31 | orangeCardHeader, 32 | greenCardHeader, 33 | redCardHeader, 34 | blueCardHeader, 35 | purpleCardHeader, 36 | cardContent: { 37 | textAlign: "right", 38 | paddingTop: "10px", 39 | padding: "15px 20px" 40 | }, 41 | cardIcon: { 42 | width: "40px", 43 | height: "36px", 44 | fill: "#fff" 45 | }, 46 | cardAvatar: { 47 | margin: "8px" 48 | }, 49 | cardCategory: { 50 | marginBottom: "0", 51 | color: grayColor, 52 | margin: "0 0 10px", 53 | ...defaultFont 54 | }, 55 | cardTitle: { 56 | margin: "0", 57 | ...defaultFont, 58 | fontSize: "1.625em" 59 | }, 60 | cardTitleSmall: { 61 | fontSize: "65%", 62 | fontWeight: "400", 63 | lineHeight: "1", 64 | color: "#777" 65 | }, 66 | cardActions: { 67 | ...cardActions, 68 | padding: "0!important" 69 | }, 70 | cardStats: { 71 | lineHeight: "22px", 72 | color: grayColor, 73 | fontSize: "12px", 74 | display: "inline-block", 75 | margin: "0!important" 76 | }, 77 | cardStatsIcon: { 78 | position: "relative", 79 | top: "4px", 80 | width: "16px", 81 | height: "16px" 82 | }, 83 | warningCardStatsIcon: { 84 | color: warningColor 85 | }, 86 | primaryCardStatsIcon: { 87 | color: primaryColor 88 | }, 89 | dangerCardStatsIcon: { 90 | color: dangerColor 91 | }, 92 | successCardStatsIcon: { 93 | color: successColor 94 | }, 95 | infoCardStatsIcon: { 96 | color: infoColor 97 | }, 98 | roseCardStatsIcon: { 99 | color: roseColor 100 | }, 101 | grayCardStatsIcon: { 102 | color: grayColor 103 | }, 104 | cardStatsLink: { 105 | color: primaryColor, 106 | textDecoration: "none", 107 | ...defaultFont 108 | } 109 | }; 110 | 111 | export default statsCardStyle; 112 | -------------------------------------------------------------------------------- /src/web/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/web/src/views/Dashboard/MiningCommands.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid } from "material-ui"; 3 | 4 | import { 5 | ItemGrid, 6 | Button 7 | } from "components"; 8 | 9 | class MiningCommands extends React.Component { 10 | 11 | discover() { 12 | this.callApiCommand("discover") 13 | .then(res => this.setState({ })) 14 | .catch(err => console.log(err)); 15 | } 16 | provision() { 17 | this.callApiCommand("poolconfigurationchanged") 18 | .then(res => this.setState({ })) 19 | .catch(err => console.log(err)); 20 | } 21 | monitor() { 22 | this.callApiCommand("monitor") 23 | .then(res => this.setState({ })) 24 | .catch(err => console.log(err)); 25 | } 26 | 27 | callApiCommand = async (commandname) => { 28 | const response = await fetch("/api/sendcommand", { 29 | method: "POST", 30 | headers: { 31 | Accept: "application/json", 32 | "Content-Type": "application/json", 33 | }, 34 | body: JSON.stringify({ 35 | command: commandname 36 | }), 37 | }); 38 | const body = await response.json(); 39 | if (response.status !== 200) { throw Error(body.message); } 40 | return body; 41 | }; 42 | 43 | render() { 44 | return ( 45 | 46 | 47 | 48 | 49 | 50 | 57 | 58 | 59 | 66 | 67 | 68 | 75 | 76 | 77 |
    78 |
    79 | 80 |
    81 |
    82 | ); 83 | } 84 | } 85 | 86 | export default MiningCommands; 87 | -------------------------------------------------------------------------------- /src/web/src/components/Cards/StatsCard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | withStyles, 4 | Card, 5 | CardContent, 6 | CardHeader, 7 | CardActions, 8 | Typography 9 | } from "material-ui"; 10 | import PropTypes from "prop-types"; 11 | 12 | import statsCardStyle from "assets/jss/material-dashboard-react/statsCardStyle"; 13 | 14 | function StatsCard({ ...props }) { 15 | const { 16 | classes, 17 | title, 18 | description, 19 | statLink, 20 | small, 21 | statText, 22 | statIconColor, 23 | iconColor 24 | } = props; 25 | return ( 26 | 27 | } 33 | /> 34 | 35 | 36 | {title} 37 | 38 | 43 | {description}{" "} 44 | {small !== undefined ? ( 45 | {small} 46 | ) : null} 47 | 48 | 49 | 50 |
    51 | {" "} 58 | {statLink !== undefined ? ( 59 | 60 | {statLink.text} 61 | 62 | ) : statText !== undefined ? ( 63 | statText 64 | ) : null} 65 |
    66 |
    67 |
    68 | ); 69 | } 70 | 71 | StatsCard.defaultProps = { 72 | iconColor: "purple", 73 | statIconColor: "gray" 74 | }; 75 | 76 | StatsCard.propTypes = { 77 | classes: PropTypes.object.isRequired, 78 | icon: PropTypes.func.isRequired, 79 | iconColor: PropTypes.oneOf(["orange", "green", "red", "blue", "purple"]), 80 | title: PropTypes.node, 81 | description: PropTypes.node, 82 | small: PropTypes.node, 83 | statIcon: PropTypes.func.isRequired, 84 | statIconColor: PropTypes.oneOf([ 85 | "warning", 86 | "primary", 87 | "danger", 88 | "success", 89 | "info", 90 | "rose", 91 | "gray" 92 | ]), 93 | statLink: PropTypes.object, 94 | statText: PropTypes.node 95 | }; 96 | 97 | export default withStyles(statsCardStyle)(StatsCard); 98 | -------------------------------------------------------------------------------- /src/web/src/views/Sensors/SensorList.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | //import PropTypes from "prop-types"; 3 | //import { withStyles } from "material-ui/styles"; 4 | import Dialog, {DialogContent, DialogActions} from "material-ui/Dialog"; 5 | import Button from "material-ui/Button"; 6 | import CameraButton from "./CameraButton"; 7 | //import humidity from "./icons/humidity.svg"; 8 | //import temperature from "./icons/temperature.svg"; 9 | import { Grid } from "material-ui"; 10 | import { 11 | StatsCard, 12 | ItemGrid 13 | } from "components"; 14 | 15 | import { 16 | SettingsInputAntenna, 17 | Info 18 | //Opacity 19 | } from "@material-ui/icons"; 20 | 21 | export default class SensorList extends Component { 22 | state = { 23 | sensors: "", 24 | selectedSensor: "", 25 | openDialog: false 26 | }; 27 | 28 | handleOpenDialog = (sensor) => { 29 | this.setState({ selectedSensor: JSON.stringify(sensor, null, 2) }); 30 | this.setState({ openDialog: true }); 31 | }; 32 | 33 | handleCloseDialog = () => { this.setState({ openDialog: false }); }; 34 | 35 | handleClick = (sensor) => { 36 | alert(JSON.stringify(sensor)); // eslint-disable-line no-alert 37 | } 38 | 39 | renderSensor(sensor) { 40 | const sens = sensor; 41 | //console.log(sens.value); 42 | return ( 43 | 44 | {this.handleOpenDialog(sens);}} 52 | /> 53 | 54 | ); 55 | } 56 | 57 | render() { 58 | const arrSensors = this.props.sensor; 59 | let renderedSensors = null; 60 | if (arrSensors) { 61 | renderedSensors = arrSensors.map((s) => this.renderSensor(s)); 62 | } 63 | return ( 64 | 65 | {renderedSensors} 66 | 67 | 68 | {this.state.openDialog && this.state.selectedSensor ? ( 69 | 73 | 74 |
    75 |                 {this.state.selectedSensor}
    76 |                 
    77 |
    78 | 79 | 82 | 83 |
    84 | ): null} 85 |
    86 | ); 87 | } 88 | } 89 | 90 | -------------------------------------------------------------------------------- /src/web/src/views/Sensors/CameraButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Button from "material-ui/Button"; 3 | import Dialog, {DialogContent, DialogActions} from "material-ui/Dialog"; 4 | //import camera from "./icons/camera.svg"; 5 | import { 6 | StatsCard, 7 | ItemGrid 8 | } from "components"; 9 | import { 10 | Store, 11 | Info 12 | } from "@material-ui/icons"; 13 | 14 | 15 | export default class CameraButton extends Component { 16 | 17 | state = { 18 | camera: "", 19 | openCamera: false 20 | }; 21 | 22 | handleOpenDialog = () => { this.setState({ openCamera: true }); }; 23 | 24 | handleCloseCamera = () => { this.setState({ openCamera: false }); }; 25 | 26 | render() { 27 | const strsensor = this.props.sensor; 28 | if (!strsensor || strsensor === " " || strsensor === "") return null; 29 | let jsensor = JSON.parse(strsensor); 30 | let sensorCamera = jsensor[0]; 31 | 32 | if (this.props.mode === "button") { 33 | return ( 34 | 35 | 36 | {this.handleOpenDialog();}} 44 | /> 45 | 46 | 47 | {this.state.openCamera && sensorCamera ? ( 48 | 52 | 53 | Camera 57 | 58 | 59 | 62 | 63 | 64 | ): null} 65 | 66 | ); 67 | } else { 68 | if (this.props.mode === "small") { 69 | return ( 70 | Camera 74 | ); 75 | } else { 76 | return ( 77 | Camera 81 | ); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/web/src/components/index.js: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // Cards 3 | // ############################# 4 | 5 | import ChartCard from './Cards/ChartCard'; 6 | import ProfileCard from './Cards/ProfileCard'; 7 | import RegularCard from './Cards/RegularCard'; 8 | import StatsCard from './Cards/StatsCard'; 9 | import TasksCard from './Cards/TasksCard'; 10 | 11 | // ############################## 12 | // // // CustomButtons 13 | // ############################# 14 | 15 | import Button from './CustomButtons/Button'; 16 | import IconButton from './CustomButtons/IconButton'; 17 | import DonateButton from './CustomButtons/DonateButton'; 18 | 19 | // ############################## 20 | // // // CustomInput 21 | // ############################# 22 | 23 | import CustomInput from './CustomInput/CustomInput'; 24 | 25 | // ############################## 26 | // // // Footer 27 | // ############################# 28 | 29 | import Footer from './Footer/Footer'; 30 | 31 | // ############################## 32 | // // // Grid 33 | // ############################# 34 | 35 | import ItemGrid from './Grid/ItemGrid'; 36 | 37 | // ############################## 38 | // // // Header 39 | // ############################# 40 | 41 | import Header from './Header/Header'; 42 | import HeaderLinks from './Header/HeaderLinks'; 43 | 44 | // ############################## 45 | // // // Sidebar 46 | // ############################# 47 | 48 | import Sidebar from './Sidebar/Sidebar'; 49 | 50 | // ############################## 51 | // // // Snackbar 52 | // ############################# 53 | 54 | import Snackbar from './Snackbar/Snackbar'; 55 | import SnackbarContent from './Snackbar/SnackbarContent'; 56 | 57 | // ############################## 58 | // // // Table 59 | // ############################# 60 | 61 | import Table from './Table/Table'; 62 | 63 | // ############################## 64 | // // // Tasks 65 | // ############################# 66 | 67 | import Tasks from './Tasks/Tasks'; 68 | 69 | // ############################## 70 | // // // Typography 71 | // ############################# 72 | 73 | import P from './Typography/P'; 74 | import Quote from './Typography/Quote'; 75 | import Muted from './Typography/Muted'; 76 | import Primary from './Typography/Primary'; 77 | import Info from './Typography/Info'; 78 | import Success from './Typography/Success'; 79 | import Warning from './Typography/Warning'; 80 | import Danger from './Typography/Danger'; 81 | import Small from './Typography/Small'; 82 | import A from './Typography/A'; 83 | 84 | export { 85 | // Cards 86 | ChartCard, 87 | ProfileCard, 88 | RegularCard, 89 | StatsCard, 90 | TasksCard, 91 | // CustomButtons 92 | Button, 93 | IconButton, 94 | DonateButton, 95 | // CustomInput 96 | CustomInput, 97 | // Footer 98 | Footer, 99 | // Grid 100 | ItemGrid, 101 | // Header 102 | Header, 103 | HeaderLinks, 104 | // Sidebar 105 | Sidebar, 106 | // Snackbar 107 | Snackbar, 108 | SnackbarContent, 109 | // Table 110 | Table, 111 | // Tasks 112 | Tasks, 113 | // Typography 114 | P, 115 | Quote, 116 | Muted, 117 | Primary, 118 | Info, 119 | Success, 120 | Warning, 121 | Danger, 122 | Small, 123 | A, 124 | }; 125 | -------------------------------------------------------------------------------- /src/web/src/views/Dashboard/MinerSummary.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { 4 | CheckCircle, 5 | HighlightOff, 6 | Warning, 7 | Cancel, 8 | Info 9 | } from "@material-ui/icons"; 10 | import { withStyles, Grid } from "material-ui"; 11 | 12 | import { 13 | StatsCard, 14 | ItemGrid 15 | } from "components"; 16 | 17 | import dashboardStyle from "assets/jss/material-dashboard-react/dashboardStyle"; 18 | 19 | class MinerSummary extends React.Component { 20 | state = { 21 | knownminers: "", 22 | value: 0 23 | }; 24 | 25 | componentDidMount() { 26 | this.callApiGetMiners() 27 | .then(res => this.setState({ knownminers: res.knownminers })) 28 | .catch(err => console.log(err)); 29 | } 30 | 31 | callApiGetMiners = async () => { 32 | const response = await fetch("/api/knownminers"); 33 | const body = await response.json(); 34 | if (response.status !== 200) { throw Error(body.message); } 35 | return body; 36 | }; 37 | 38 | handleChange = (event, value) => { 39 | this.setState({ value }); 40 | }; 41 | 42 | handleChangeIndex = index => { 43 | this.setState({ value: index }); 44 | }; 45 | 46 | render() { 47 | const jminers = JSON.parse(JSON.stringify(this.state.knownminers)); 48 | const arrMiners = []; 49 | if (jminers != null){ 50 | Object.keys(jminers).forEach(function(key) { 51 | arrMiners.push(JSON.parse(jminers[key], function (key, value) { 52 | return (value == null) ? "" : value 53 | })); 54 | }); 55 | } 56 | var counts = arrMiners.reduce((result, miner) => { 57 | var status = miner.status; 58 | if (!result.hasOwnProperty(status)) { 59 | result[status] = 0; 60 | } 61 | result[status]++; 62 | return result; 63 | }, {}); 64 | 65 | return ( 66 |
    67 | 68 | 69 | 77 | 78 | 79 | 89 | 90 | 91 | 99 | 100 | 101 | 102 |
    103 | ); 104 | } 105 | } 106 | 107 | MinerSummary.propTypes = { 108 | classes: PropTypes.object.isRequired 109 | }; 110 | 111 | export default withStyles(dashboardStyle)(MinerSummary); 112 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/headerLinksStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // HeaderLinks styles 3 | // ############################# 4 | 5 | import { 6 | defaultFont, 7 | dangerColor, 8 | primaryColor, 9 | primaryBoxShadow 10 | } from "assets/jss/material-dashboard-react.jsx"; 11 | 12 | const headerLinksStyle = theme => ({ 13 | popperClose: { 14 | pointerEvents: "none" 15 | }, 16 | search: { 17 | "& > div": { 18 | marginTop: "0" 19 | }, 20 | [theme.breakpoints.down("sm")]: { 21 | margin: "10px 15px", 22 | float: "none !important", 23 | paddingTop: "1px", 24 | paddingBottom: "1px", 25 | padding: "10px 15px", 26 | width: "auto", 27 | marginTop: "40px" 28 | } 29 | }, 30 | linkText: { 31 | zIndex: "4", 32 | ...defaultFont, 33 | fontSize: "14px" 34 | }, 35 | buttonLink: { 36 | [theme.breakpoints.down("sm")]: { 37 | display: "flex", 38 | marginLeft: "30px", 39 | width: "auto" 40 | } 41 | }, 42 | searchButton: { 43 | [theme.breakpoints.down("sm")]: { 44 | top: "-50px !important", 45 | marginRight: "22px", 46 | float: "right" 47 | } 48 | }, 49 | margin: { 50 | zIndex: "4", 51 | margin: "0", 52 | }, 53 | searchIcon: { 54 | width: "17px", 55 | zIndex: "4" 56 | }, 57 | links: { 58 | width: "20px", 59 | height: "20px", 60 | zIndex: "4", 61 | [theme.breakpoints.down("sm")]: { 62 | display: "block", 63 | width: "30px", 64 | height: "30px", 65 | color: "#a9afbb", 66 | marginRight: "15px" 67 | } 68 | }, 69 | notifications: { 70 | zIndex: "4", 71 | [theme.breakpoints.up("md")]: { 72 | position: "absolute", 73 | top: "5px", 74 | border: "1px solid #FFF", 75 | right: "10px", 76 | fontSize: "9px", 77 | background: dangerColor, 78 | color: "#FFFFFF", 79 | minWidth: "16px", 80 | height: "16px", 81 | borderRadius: "10px", 82 | textAlign: "center", 83 | lineHeight: "16px", 84 | verticalAlign: "middle", 85 | display: "block" 86 | }, 87 | [theme.breakpoints.down("sm")]: { 88 | ...defaultFont, 89 | fontSize: "14px", 90 | marginRight: "8px" 91 | } 92 | }, 93 | dropdown: { 94 | borderRadius: "3px", 95 | border: "0", 96 | boxShadow: "0 2px 5px 0 rgba(0, 0, 0, 0.26)", 97 | top: "100%", 98 | zIndex: "1000", 99 | minWidth: "160px", 100 | padding: "5px 0", 101 | margin: "2px 0 0", 102 | fontSize: "14px", 103 | textAlign: "left", 104 | listStyle: "none", 105 | backgroundColor: "#fff", 106 | backgroundClip: "padding-box" 107 | }, 108 | pooperResponsive: { 109 | [theme.breakpoints.down("sm")]: { 110 | zIndex: "1640", 111 | position: "static", 112 | float: "none", 113 | width: "auto", 114 | marginTop: "0", 115 | backgroundColor: "transparent", 116 | border: "0", 117 | boxShadow: "none", 118 | color: "black" 119 | } 120 | }, 121 | dropdownItem: { 122 | ...defaultFont, 123 | fontSize: "13px", 124 | padding: "10px 20px", 125 | margin: "0 5px", 126 | borderRadius: "2px", 127 | transition: "all 150ms linear", 128 | display: "block", 129 | clear: "both", 130 | fontWeight: "400", 131 | lineHeight: "1.42857143", 132 | color: "#333", 133 | whiteSpace: "nowrap", 134 | "&:hover": { 135 | backgroundColor: primaryColor, 136 | color: "#FFFFFF", 137 | ...primaryBoxShadow 138 | } 139 | } 140 | }); 141 | 142 | export default headerLinksStyle; 143 | -------------------------------------------------------------------------------- /src/web/src/views/Sensors/Sensors.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | //import { Grid } from "material-ui"; 3 | 4 | import SensorList from "./SensorList"; 5 | 6 | class Sensors extends React.Component { 7 | constructor() { 8 | super(); 9 | this.state = { sensors: [] }; 10 | } 11 | 12 | state = { 13 | sensors: [], 14 | camera: null, 15 | } 16 | 17 | supportsSSE() { 18 | return !!window.EventSource; 19 | } 20 | 21 | componentDidMount() { 22 | this.callApiGetSensors() 23 | .then(res => { 24 | const arrSensors = []; 25 | if (res.knownsensors != null){ 26 | Object.keys(res.knownsensors).forEach(function(key) { 27 | arrSensors.push(JSON.parse(res.knownsensors[key], function (key, value) { 28 | return (value == null) ? "" : value; 29 | })); 30 | }); 31 | }; 32 | this.setState({ sensors: arrSensors }); 33 | }) 34 | .catch(err => console.log(err)); 35 | 36 | this.callApiGetCamera() 37 | .then(res => this.setState({ camera: res.camera })) 38 | .catch(err => console.log(err)); 39 | 40 | if (this.supportsSSE() && !this.eventListener) { 41 | this.eventListener = new EventSource("/sse"); 42 | this.subscribe(this.eventListener); 43 | } 44 | } 45 | 46 | componentWillUnmount() { 47 | if (this.eventListener){ 48 | this.eventListener.close(); 49 | //console.log("Sensors: unsubscribed"); 50 | } 51 | } 52 | 53 | callApiGetSensors = async () => { 54 | const response = await fetch("/api/knownsensors"); 55 | const body = await response.json(); 56 | if (response.status !== 200) throw Error(body.message); 57 | return body; 58 | }; 59 | 60 | callApiGetCamera = async () => { 61 | const response = await fetch("/api/getcamera"); 62 | const body = await response.json(); 63 | if (response.status !== 200) throw Error(body.message); 64 | return body; 65 | }; 66 | 67 | subscribe(es) { 68 | const that = this; 69 | if (!es) return; 70 | 71 | es.addEventListener("full-cycle-sensor", (e) => { 72 | //var d = new Date(); 73 | //let txt = d.toLocaleString() + ": EventSource: " + e.data; 74 | //console.log(txt); 75 | that.addSensor(e.data); 76 | }, false); 77 | 78 | //console.log("Sensors: subscribed"); 79 | 80 | } 81 | 82 | addSensor = (sensorMessage) => { 83 | const msgJson = JSON.parse(sensorMessage); 84 | const sensorvalue = JSON.parse(msgJson.body)[0]; 85 | this.updateSensor(sensorvalue); 86 | } 87 | 88 | updateSensor = (sensorvalue) => { 89 | var index = this.state.sensors.findIndex(x=> x.sensorid === sensorvalue.sensorid); 90 | if (index === -1) 91 | { 92 | this.setState({ 93 | sensors: [ sensorvalue, ...this.state.sensors ] 94 | }); 95 | } 96 | else { 97 | this.setState({ 98 | sensors: [ 99 | ...this.state.sensors.slice(0,index), 100 | Object.assign({}, this.state.sensors[index], sensorvalue), 101 | ...this.state.sensors.slice(index+1) 102 | ] 103 | }); 104 | } 105 | } 106 | 107 | render() { 108 | const jsensors = this.state.sensors; 109 | let jcamera = null; 110 | try { 111 | jcamera = JSON.parse(JSON.stringify(this.state.camera)); 112 | } 113 | catch (error) { 114 | jcamera = null; 115 | }; 116 | return ( 117 |
    118 | 119 |
    120 | ); 121 | } 122 | } 123 | 124 | export default Sensors; 125 | -------------------------------------------------------------------------------- /src/web/src/components/Tasks/Tasks.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | withStyles, 4 | Checkbox, 5 | IconButton, 6 | Table, 7 | TableBody, 8 | TableCell, 9 | TableRow, 10 | Tooltip 11 | } from "material-ui"; 12 | import { Edit, Close, Check } from "@material-ui/icons"; 13 | 14 | import PropTypes from "prop-types"; 15 | 16 | import tasksStyle from "assets/jss/material-dashboard-react/tasksStyle.jsx"; 17 | 18 | class Tasks extends React.Component { 19 | state = { 20 | checked: this.props.checkedIndexes 21 | }; 22 | handleToggle = value => () => { 23 | const { checked } = this.state; 24 | const currentIndex = checked.indexOf(value); 25 | const newChecked = [...checked]; 26 | 27 | if (currentIndex === -1) { 28 | newChecked.push(value); 29 | } else { 30 | newChecked.splice(currentIndex, 1); 31 | } 32 | 33 | this.setState({ 34 | checked: newChecked 35 | }); 36 | }; 37 | render() { 38 | const { classes, tasksIndexes, tasks } = this.props; 39 | return ( 40 | 41 | 42 | {tasksIndexes.map(value => ( 43 | 44 | 45 | } 50 | icon={} 51 | classes={{ 52 | checked: classes.checked 53 | }} 54 | /> 55 | 56 | 57 | {tasks[value]} 58 | 59 | 60 | 66 | 70 | 75 | 76 | 77 | 83 | 87 | 92 | 93 | 94 | 95 | 96 | ))} 97 | 98 |
    99 | ); 100 | } 101 | } 102 | 103 | Tasks.propTypes = { 104 | classes: PropTypes.object.isRequired, 105 | tasksIndexes: PropTypes.arrayOf(PropTypes.number), 106 | tasks: PropTypes.arrayOf(PropTypes.node) 107 | }; 108 | 109 | export default withStyles(tasksStyle)(Tasks); 110 | -------------------------------------------------------------------------------- /src/web/src/components/Sidebar/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { NavLink } from "react-router-dom"; 4 | import cx from "classnames"; 5 | import { 6 | withStyles, 7 | Drawer, 8 | Hidden, 9 | List, 10 | ListItem, 11 | ListItemIcon, 12 | ListItemText 13 | } from "material-ui"; 14 | 15 | import { HeaderLinks } from "components"; 16 | 17 | import sidebarStyle from "assets/jss/material-dashboard-react/sidebarStyle.jsx"; 18 | 19 | const Sidebar = ({ ...props }) => { 20 | // verifies if routeName is the one active (in browser input) 21 | function activeRoute(routeName) { 22 | return props.location.pathname.indexOf(routeName) > -1 ? true : false; 23 | } 24 | const { classes, color, logo, image, logoText, routes } = props; 25 | var links = ( 26 | 27 | {routes.map((prop, key) => { 28 | if (prop.redirect) { return null; } 29 | const listItemClasses = cx({ 30 | [" " + classes[color]]: activeRoute(prop.path) 31 | }); 32 | const whiteFontClasses = cx({ 33 | [" " + classes.whiteFont]: activeRoute(prop.path) 34 | }); 35 | return ( 36 | 42 | 43 | 44 | 45 | 46 | 51 | 52 | 53 | ); 54 | })} 55 | 56 | ); 57 | var brand = ( 58 | 66 | ); 67 | return ( 68 |
    69 | 70 | 82 | {brand} 83 |
    84 | 85 | {links} 86 |
    87 | {image !== undefined ? ( 88 |
    92 | ) : null} 93 | 94 | 95 | 96 | 104 | {brand} 105 |
    {links}
    106 | {image !== undefined ? ( 107 |
    111 | ) : null} 112 | 113 | 114 |
    115 | ); 116 | }; 117 | 118 | Sidebar.propTypes = { 119 | classes: PropTypes.object.isRequired 120 | }; 121 | 122 | export default withStyles(sidebarStyle)(Sidebar); 123 | -------------------------------------------------------------------------------- /src/web/src/components/Cards/TasksCard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { 4 | withStyles, 5 | Card, 6 | CardContent, 7 | CardHeader, 8 | Typography, 9 | Tabs, 10 | Tab 11 | } from "material-ui"; 12 | import { BugReport, Code, Cloud } from "@material-ui/icons"; 13 | 14 | import { Tasks } from "components"; 15 | 16 | import { bugs, website, server } from "variables/general"; 17 | 18 | import tasksCardStyle from "assets/jss/material-dashboard-react/tasksCardStyle"; 19 | 20 | class TasksCard extends React.Component { 21 | state = { 22 | value: 0 23 | }; 24 | handleChange = (_event, value) => { 25 | this.setState({ value }); 26 | }; 27 | render() { 28 | const { classes } = this.props; 29 | return ( 30 | 31 | 48 | } 56 | label={"Bugs"} 57 | /> 58 | } 66 | label={"Website"} 67 | /> 68 | } 76 | label={"Server"} 77 | /> 78 | 79 | } 80 | /> 81 | 82 | {this.state.value === 0 && ( 83 | 84 | 89 | 90 | )} 91 | {this.state.value === 1 && ( 92 | 93 | 98 | 99 | )} 100 | {this.state.value === 2 && ( 101 | 102 | 107 | 108 | )} 109 | 110 | 111 | ); 112 | } 113 | } 114 | 115 | TasksCard.propTypes = { 116 | classes: PropTypes.object.isRequired 117 | }; 118 | 119 | export default withStyles(tasksCardStyle)(TasksCard); 120 | -------------------------------------------------------------------------------- /src/web/src/components/Header/HeaderLinks.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classNames from "classnames"; 3 | import { Manager, Target, Popper } from "react-popper"; 4 | import { 5 | withStyles, 6 | IconButton, 7 | MenuItem, 8 | MenuList, 9 | Grow, 10 | Paper, 11 | ClickAwayListener, 12 | Hidden 13 | } from "material-ui"; 14 | import { Person, Notifications, Dashboard 15 | //, Search 16 | } from "@material-ui/icons"; 17 | 18 | //import { CustomInput, IconButton as SearchButton } from "components"; 19 | 20 | import headerLinksStyle from "assets/jss/material-dashboard-react/headerLinksStyle"; 21 | 22 | class HeaderLinks extends React.Component { 23 | state = { 24 | open: false 25 | }; 26 | handleClick = () => { 27 | this.setState({ open: !this.state.open }); 28 | }; 29 | 30 | handleClose = () => { 31 | this.setState({ open: false }); 32 | }; 33 | 34 | // className={classes.dropdownItem} 35 | renderAlert(alert) { 36 | const { classes } = this.props; 37 | return ( 38 | 43 | {alert && alert.slice(0,150)} 44 | 45 | ); 46 | } 47 | 48 | render() { 49 | const { classes } = this.props; 50 | const { open } = this.state; 51 | const renderedAlerts = this.props.alerts && this.props.alerts.map((a) => this.renderAlert(a)); 52 | 53 | return ( 54 |
    55 | 60 | 61 | 62 |

    Dashboard

    63 |
    64 |
    65 | 66 | 67 | 75 | 77 | 78 | {renderedAlerts ? renderedAlerts.length : 0} 79 | 80 | 81 |

    82 | Notification 83 |

    84 |
    85 |
    86 |
    87 | 96 | 97 | 102 | 103 | 104 | {renderedAlerts} 105 | 106 | 107 | 108 | 109 | 110 |
    111 | 116 | 117 | 118 |

    Profile

    119 |
    120 |
    121 |
    122 | ); 123 | } 124 | } 125 | 126 | export default withStyles(headerLinksStyle)(HeaderLinks); 127 | -------------------------------------------------------------------------------- /src/web/src/views/Miners/Miners.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid } from "material-ui"; 3 | import { RegularCard, ItemGrid } from "components"; 4 | 5 | import MinersTable from "./MinersTable"; 6 | 7 | class Miners extends React.Component { 8 | constructor() { 9 | super(); 10 | this.state = { knownminers: [] }; 11 | } 12 | 13 | state = { 14 | knownminers: [], 15 | }; 16 | 17 | supportsSSE() { 18 | return !!window.EventSource; 19 | } 20 | 21 | componentDidMount() { 22 | this.callApiGetMiners() 23 | .then(res => { 24 | const arrMiners = []; 25 | if (res.knownminers != null){ 26 | Object.keys(res.knownminers).forEach(function(key) { 27 | arrMiners.push(JSON.parse(res.knownminers[key], function (key, value) { 28 | return (value == null) ? "" : value 29 | })); 30 | }); 31 | } 32 | this.setState({ knownminers: arrMiners }); 33 | }) 34 | .catch(err => console.log(err)); 35 | 36 | if (this.supportsSSE() && !this.eventListener) { 37 | this.eventListener = new EventSource("/sse"); 38 | this.subscribe(this.eventListener); 39 | } 40 | } 41 | 42 | componentWillUnmount() { 43 | if (this.eventListener){ 44 | this.eventListener.close(); 45 | //console.log("Miners: unsubscribed"); 46 | } 47 | } 48 | 49 | callApiGetMiners = async () => { 50 | const response = await fetch('/api/knownminers'); 51 | const body = await response.json(); 52 | if (response.status !== 200) throw Error(body.message); 53 | return JSON.parse(JSON.stringify(body)); 54 | }; 55 | 56 | subscribe(es) { 57 | const that = this; 58 | if (!es) return; 59 | 60 | es.addEventListener("full-cycle-miner", (e) => { 61 | that.addMiner(e.data); 62 | }, false); 63 | //console.log("Miners: subscribed"); 64 | 65 | } 66 | 67 | addMiner = (minerMessage) => { 68 | const msgJson = JSON.parse(minerMessage); 69 | const minerStats = JSON.parse(msgJson.body)[0]; 70 | //todo: should use key property 71 | //console.log("Miner:"+minerStats.miner.lastmonitor+":"+minerStats.miner.name); 72 | this.updateMiner(this.getMinerKey(minerStats.miner), minerStats.miner); 73 | } 74 | 75 | getMinerKey(miner) { 76 | if (!!miner.minerid) { return miner.minerid; } 77 | if (!!miner.networkid) { return miner.networkid; } 78 | return miner.name; 79 | } 80 | 81 | updateMiner = (key, miner) => { 82 | var index = this.state.knownminers.findIndex(x=> x.name === miner.name); 83 | if (index === -1) 84 | { 85 | //console.log("updateMiner: adding " + miner.name); 86 | this.setState({ 87 | knownminers: [ miner, ...this.state.knownminers ] 88 | }); 89 | } 90 | else { 91 | //console.log("updateMiner: replacing " + miner.name + "@" + index.toString()); 92 | this.setState({ 93 | knownminers: [ 94 | ...this.state.knownminers.slice(0,index), 95 | Object.assign({}, this.state.knownminers[index], miner), 96 | ...this.state.knownminers.slice(index+1) 97 | ] 98 | }); 99 | } 100 | } 101 | 102 | render() { 103 | const jminers = this.state.knownminers; 104 | return ( 105 | 106 | 107 | 112 | } 113 | /> 114 | 115 | 116 | ); 117 | } 118 | } 119 | 120 | export default Miners; 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Full Cycle Web and API site 2 | 3 | [![Build Status](https://scrutinizer-ci.com/g/dfoderick/fullcyclereact/badges/build.png?b=master)](https://scrutinizer-ci.com/g/dfoderick/fullcyclereact/build-status/master) 4 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/04e7d62fa77c4aae80cdf092c245c9eb)](https://app.codacy.com/app/dfoderick/fullcyclereact?utm_source=github.com&utm_medium=referral&utm_content=dfoderick/fullcyclereact&utm_campaign=Badge_Grade_Dashboard) 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/dfoderick/fullcyclereact/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/dfoderick/fullcyclereact/?branch=master) 6 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BMHSEUYDP9XWW) 7 | 8 | This project is the front end (UI) React web site for the Full Cycle Bitcoin Mining Controller. 9 | Installation of the back end is required and can be found at https://github.com/dfoderick/fullcycle 10 | ![Full Cycle React](src/web/src/images/FullCycleReact.png?raw=true "Full Cycle React") 11 | ![Full Cycle Switch Pool](src/web/src/images/fullcycle_switch.png?raw=true "Full Cycle Switch Pool") 12 | ![Full Cycle Reset Miner](src/web/src/images/fullcycle_reset.png?raw=true "Full Cycle Reset Miner") 13 | # Installation 14 | The web sites can be installed on the same Raspberry Pi as the Full Cycle Controller 15 | or they can be installed on a separate computer. 16 | 17 | By far the easiest option is to install Docker on the Raspberry Pi 18 | and run the sites inside Docker containers. 19 | 20 | # Docker install on Raspberry Pi 21 | If you do not have Docker installed on your Raspberry Pi then follow these 22 | instructions. 23 | ``` 24 | sudo apt-get install -y apt-transport-https 25 | sudo curl -sSL https://get.docker.com | sudo sh 26 | sudo systemctl enable docker 27 | sudo usermod -aG docker pi 28 | ``` 29 | You will need to logout and log back in for permission to take effect. When you 30 | log back in then check your Docker installation. 31 | 32 | ``` 33 | docker info 34 | ``` 35 | If you get information about your Docker program then you are ready to go. 36 | # Install as Docker containers 37 | 38 | The Full Cycle web sites are hosted on Docker Hub (https://hub.docker.com/r/fullcycle/web/) and can be installed using 39 | these commands. 40 | ``` 41 | docker run --name fullcycleweb -d --network=host --restart unless-stopped fullcycle/web 42 | ``` 43 | (Note that API and static pages have been combined into one site in production.) 44 | Once installed, test the API to make sure it responds. Browse to http://raspberrypi.local:5000/api/hello 45 | and it should respond with 46 | ``` 47 | {"express":"Welcome to Full Cycle Mining"} 48 | ``` 49 | (Replace `raspberrypi.local` with the ip address of your raspberrypi 50 | if your pi does not have a network name.) 51 | 52 | Then browse to the Web site `http://raspberrypi.local:5000/`. 53 | 54 | !Important! The web page will be blank or show an error if the back end Full Cycle Mining Controller is not running. (Install from https://github.com/dfoderick/fullcycle) If everything is working then you will see the web site 55 | similar to the screenshots above. 56 | 57 | If you have problems installing or want to give feed back then 58 | add an issue to this project. 59 | 60 | Dave Foderick 61 | dfoderick@gmail.com 62 | 63 | # Install directly on the Operating system 64 | If you cannot install using Docker then follow these steps. 65 | 66 | Check which versions of node and npm are installed in your system. 67 | ``` 68 | node -v 69 | v9.11.1 70 | npm -v 71 | 5.6.0 72 | ``` 73 | If older than 9.11 and 5.6 then install the update for Node directly from Debian. 74 | ``` 75 | sudo curl -sL https://deb.nodesource.com/setup_9.x | sudo bash - 76 | sudo apt-get install -y nodejs 77 | ``` 78 | Install dependencies using the following commands. 79 | ``` 80 | cd ~/ 81 | git clone https://github.com/dfoderick/fullcyclereact.git 82 | cd ~/fullcyclereact/src/api 83 | sudo npm install 84 | sudo npm install nodemon 85 | sudo npm run prod 86 | ``` 87 | You will need to log in to another session then run these commands. 88 | ``` 89 | sudo npm install -g serve 90 | cd ~/fullcyclereact/src/web 91 | sudo npm install 92 | npm run build 93 | serve -p 3000 build 94 | ``` 95 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/iconButtonStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // IconButton styles 3 | // ############################# 4 | 5 | import { 6 | grayColor, 7 | roseColor, 8 | primaryColor, 9 | infoColor, 10 | successColor, 11 | warningColor, 12 | dangerColor 13 | } from "assets/jss/material-dashboard-react.jsx"; 14 | 15 | const iconButtonStyle = { 16 | button: { 17 | height: "40px", 18 | minWidth: "40px", 19 | width: "40px", 20 | borderRadius: "50%", 21 | fontSize: "24px", 22 | margin: "auto", 23 | padding: "0", 24 | boxShadow: 25 | "0 2px 2px 0 rgba(153, 153, 153, 0.14), 0 3px 1px -2px rgba(153, 153, 153, 0.2), 0 1px 5px 0 rgba(153, 153, 153, 0.12)", 26 | overflow: "hidden", 27 | position: "relative", 28 | lineHeight: "normal", 29 | border: "none", 30 | fontWeight: "400", 31 | textTransform: "uppercase", 32 | letterSpacing: "0", 33 | willChange: "box-shadow, transform", 34 | transition: 35 | "box-shadow 0.2s cubic-bezier(0.4, 0, 1, 1), background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1)", 36 | display: "inline-block", 37 | textAlign: "center", 38 | whiteSpace: "nowrap", 39 | verticalAlign: "middle", 40 | touchAction: "manipulation", 41 | cursor: "pointer", 42 | userSelect: "none", 43 | backgroundImage: "none", 44 | backgroundColor: grayColor, 45 | "&:hover": { 46 | backgroundColor: grayColor, 47 | boxShadow: 48 | "0 14px 26px -12px rgba(153, 153, 153, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(153, 153, 153, 0.2)" 49 | } 50 | }, 51 | primary: { 52 | backgroundColor: primaryColor, 53 | boxShadow: 54 | "0 2px 2px 0 rgba(156, 39, 176, 0.14), 0 3px 1px -2px rgba(156, 39, 176, 0.2), 0 1px 5px 0 rgba(156, 39, 176, 0.12)", 55 | "&:hover": { 56 | backgroundColor: primaryColor, 57 | boxShadow: 58 | "0 14px 26px -12px rgba(156, 39, 176, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(156, 39, 176, 0.2)" 59 | } 60 | }, 61 | info: { 62 | backgroundColor: infoColor, 63 | boxShadow: 64 | "0 2px 2px 0 rgba(0, 188, 212, 0.14), 0 3px 1px -2px rgba(0, 188, 212, 0.2), 0 1px 5px 0 rgba(0, 188, 212, 0.12)", 65 | "&:hover": { 66 | backgroundColor: infoColor, 67 | boxShadow: 68 | "0 14px 26px -12px rgba(0, 188, 212, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 188, 212, 0.2)" 69 | } 70 | }, 71 | success: { 72 | backgroundColor: successColor, 73 | boxShadow: 74 | "0 2px 2px 0 rgba(76, 175, 80, 0.14), 0 3px 1px -2px rgba(76, 175, 80, 0.2), 0 1px 5px 0 rgba(76, 175, 80, 0.12)", 75 | "&:hover": { 76 | backgroundColor: successColor, 77 | boxShadow: 78 | "0 14px 26px -12px rgba(76, 175, 80, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(76, 175, 80, 0.2)" 79 | } 80 | }, 81 | warning: { 82 | backgroundColor: warningColor, 83 | boxShadow: 84 | "0 2px 2px 0 rgba(255, 152, 0, 0.14), 0 3px 1px -2px rgba(255, 152, 0, 0.2), 0 1px 5px 0 rgba(255, 152, 0, 0.12)", 85 | "&:hover": { 86 | backgroundColor: warningColor, 87 | boxShadow: 88 | "0 14px 26px -12px rgba(255, 152, 0, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(255, 152, 0, 0.2)" 89 | } 90 | }, 91 | danger: { 92 | backgroundColor: dangerColor, 93 | boxShadow: 94 | "0 2px 2px 0 rgba(244, 67, 54, 0.14), 0 3px 1px -2px rgba(244, 67, 54, 0.2), 0 1px 5px 0 rgba(244, 67, 54, 0.12)", 95 | "&:hover": { 96 | backgroundColor: dangerColor, 97 | boxShadow: 98 | "0 14px 26px -12px rgba(244, 67, 54, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(244, 67, 54, 0.2)" 99 | } 100 | }, 101 | rose: { 102 | backgroundColor: roseColor, 103 | boxShadow: 104 | "0 2px 2px 0 rgba(233, 30, 99, 0.14), 0 3px 1px -2px rgba(233, 30, 99, 0.2), 0 1px 5px 0 rgba(233, 30, 99, 0.12)", 105 | "&:hover": { 106 | backgroundColor: roseColor, 107 | boxShadow: 108 | "0 14px 26px -12px rgba(233, 30, 99, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(233, 30, 99, 0.2)" 109 | } 110 | }, 111 | white: { 112 | "&,&:focus,&:hover": { 113 | backgroundColor: "#FFFFFF", 114 | color: grayColor 115 | } 116 | }, 117 | simple: { 118 | color: "#FFFFFF", 119 | background: "transparent", 120 | boxShadow: "none" 121 | } 122 | }; 123 | 124 | export default iconButtonStyle; 125 | -------------------------------------------------------------------------------- /src/api/src/api.js: -------------------------------------------------------------------------------- 1 | 2 | var express = require("express"); 3 | var buffer = require("buffer"); 4 | var router = express.Router(); 5 | const bodyParser = require("body-parser"); 6 | const redis = require("redis"); 7 | const amqp = require("amqplib/callback_api"); 8 | 9 | const services = require("./services"); 10 | const messages = require("./messages"); 11 | 12 | const jsonParser = bodyParser.json(); 13 | const urlencodedParser = bodyParser.urlencoded({ extended: false }); 14 | 15 | function bail(err, conn) { 16 | console.error("bailing..."); 17 | console.error(err); 18 | if (conn) { 19 | conn.close(function() { 20 | // if (doexit) 21 | // process.exit(1); 22 | }); 23 | } 24 | } 25 | 26 | function publish (q, msg){ 27 | //console.log(q + " => " + msg); 28 | 29 | amqp.connect(services.messagebus.connection, function(err, conn) { 30 | conn.createChannel(function(err, ch) { 31 | if (err != null) { bail(err); } 32 | ch.assertQueue(q, {durable: false}); 33 | ch.sendToQueue(q, new buffer.Buffer(msg)); 34 | //console.log(" [x] Sent %s", msg); 35 | }); 36 | //conn.close(); 37 | }); 38 | } 39 | 40 | function redisclient() { 41 | var client = redis.createClient(services.redis.port, services.redis.host, {no_ready_check: true}); 42 | client.auth(services.redis.password, function (err) { 43 | if (err) throw err; 44 | }); 45 | return client; 46 | } 47 | 48 | function getredis(key, callback) { 49 | var client = redisclient(); 50 | client.get([key], function(err, object) { 51 | callback(object); 52 | client.quit(); 53 | }); 54 | } 55 | 56 | function getredishashset(key, callback) { 57 | var client = redisclient(); 58 | client.hgetall([key], function(err, object) { 59 | callback(object); 60 | client.quit(); 61 | }); 62 | }; 63 | 64 | router.get("/hello", 65 | // passport.authenticate("basic", { session: false }), 66 | (req, res) => { 67 | //console.log("called hello"); 68 | res.send({ express: "Welcome to Full Cycle Mining" }); 69 | }); 70 | 71 | router.get("/getcamera", 72 | // passport.authenticate("basic", { session: false }), 73 | (req, res) => { 74 | //console.log("called getcamera") 75 | getredis("camera", function(object) { 76 | res.send({ camera: object }); 77 | }); 78 | }); 79 | 80 | 81 | router.get("/knownminers", 82 | // passport.authenticate("basic", { session: false }), 83 | (req, res) => { 84 | //console.log("called knownminers") 85 | getredishashset("knownminers", function(object) { 86 | res.send({ knownminers: object }); 87 | }) 88 | }); 89 | 90 | router.get("/knownpools", 91 | // passport.authenticate("basic", { session: false }), 92 | (req, res) => { 93 | //console.log("called knownpools") 94 | getredishashset("knownpools", function(object) { 95 | res.send({ knownpools: object }); 96 | }); 97 | }); 98 | 99 | router.post("/sendcommand",jsonParser, (req, res) => { 100 | publish(req.body.command,JSON.stringify(req.body.command)); 101 | }); 102 | 103 | router.post("/save",jsonParser, (req, res) => { 104 | //console.log(req.body); 105 | //1) make configmessage with command 106 | var configmsg = messages.makeConfigurationMessage(req.body); 107 | //2) wrap the configmessage into an envelope 108 | var envelope = messages.makeMessage("configuration", JSON.stringify(configmsg)) 109 | publish("save",JSON.stringify(envelope)); 110 | }); 111 | 112 | router.post("/minerrestart",jsonParser, (req, res) => { 113 | //console.log(req.body); 114 | //1) create restart minercommand 115 | var cmd = messages.makeCommand(req.body.command, req.body.parameter); 116 | //2) make minermessage with command 117 | var minermsg = messages.makeMinerMessage(req.body.miner, cmd, null); 118 | //3) wrap the minermessage into an envelope 119 | var envelope = messages.makeMessage("minercommand", JSON.stringify(minermsg)) 120 | publish("restart",JSON.stringify(envelope)); 121 | }); 122 | 123 | router.post("/minerswitchpool", jsonParser, (req, res) => { 124 | //console.log(req.body); 125 | //1) create minercommand 126 | var cmd = messages.makeCommand(req.body.command, req.body.parameter); 127 | //2) make minermessage with command 128 | var minermsg = messages.makeMinerMessage(req.body.miner, cmd, null); 129 | //3) wrap the minermessage into an envelope 130 | var envelope = messages.makeMessage("minercommand", JSON.stringify(minermsg)) 131 | publish("switch",JSON.stringify(envelope)); 132 | }); 133 | 134 | router.get("/knownsensors", 135 | //passport.authenticate("basic", { session: false }), 136 | (req, res) => { 137 | //console.log("called knownsensors"); 138 | getredishashset("knownsensors", function(object) { 139 | res.send({ knownsensors: object });; 140 | }); 141 | }); 142 | 143 | module.exports = router; 144 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/buttonStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // Button styles 3 | // ############################# 4 | 5 | import { 6 | grayColor, 7 | roseColor, 8 | primaryColor, 9 | infoColor, 10 | successColor, 11 | warningColor, 12 | dangerColor 13 | } from "assets/jss/material-dashboard-react.jsx"; 14 | 15 | const buttonStyle = { 16 | button: { 17 | backgroundColor: grayColor, 18 | color: "#FFFFFF", 19 | boxShadow: 20 | "0 2px 2px 0 rgba(153, 153, 153, 0.14), 0 3px 1px -2px rgba(153, 153, 153, 0.2), 0 1px 5px 0 rgba(153, 153, 153, 0.12)", 21 | border: "none", 22 | borderRadius: "3px", 23 | position: "relative", 24 | padding: "12px 30px", 25 | margin: "10px 1px", 26 | fontSize: "12px", 27 | fontWeight: "400", 28 | textTransform: "uppercase", 29 | letterSpacing: "0", 30 | willChange: "box-shadow, transform", 31 | transition: 32 | "box-shadow 0.2s cubic-bezier(0.4, 0, 1, 1), background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1)", 33 | lineHeight: "1.42857143", 34 | textAlign: "center", 35 | whiteSpace: "nowrap", 36 | verticalAlign: "middle", 37 | touchAction: "manipulation", 38 | cursor: "pointer", 39 | "&:hover": { 40 | backgroundColor: grayColor, 41 | boxShadow: 42 | "0 14px 26px -12px rgba(153, 153, 153, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(153, 153, 153, 0.2)" 43 | } 44 | }, 45 | fullWidth: { 46 | width: "100%" 47 | }, 48 | primary: { 49 | backgroundColor: primaryColor, 50 | boxShadow: 51 | "0 2px 2px 0 rgba(156, 39, 176, 0.14), 0 3px 1px -2px rgba(156, 39, 176, 0.2), 0 1px 5px 0 rgba(156, 39, 176, 0.12)", 52 | "&:hover": { 53 | backgroundColor: primaryColor, 54 | boxShadow: 55 | "0 14px 26px -12px rgba(156, 39, 176, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(156, 39, 176, 0.2)" 56 | } 57 | }, 58 | info: { 59 | backgroundColor: infoColor, 60 | boxShadow: 61 | "0 2px 2px 0 rgba(0, 188, 212, 0.14), 0 3px 1px -2px rgba(0, 188, 212, 0.2), 0 1px 5px 0 rgba(0, 188, 212, 0.12)", 62 | "&:hover": { 63 | backgroundColor: infoColor, 64 | boxShadow: 65 | "0 14px 26px -12px rgba(0, 188, 212, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 188, 212, 0.2)" 66 | } 67 | }, 68 | success: { 69 | backgroundColor: successColor, 70 | boxShadow: 71 | "0 2px 2px 0 rgba(76, 175, 80, 0.14), 0 3px 1px -2px rgba(76, 175, 80, 0.2), 0 1px 5px 0 rgba(76, 175, 80, 0.12)", 72 | "&:hover": { 73 | backgroundColor: successColor, 74 | boxShadow: 75 | "0 14px 26px -12px rgba(76, 175, 80, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(76, 175, 80, 0.2)" 76 | } 77 | }, 78 | warning: { 79 | backgroundColor: warningColor, 80 | boxShadow: 81 | "0 2px 2px 0 rgba(255, 152, 0, 0.14), 0 3px 1px -2px rgba(255, 152, 0, 0.2), 0 1px 5px 0 rgba(255, 152, 0, 0.12)", 82 | "&:hover": { 83 | backgroundColor: warningColor, 84 | boxShadow: 85 | "0 14px 26px -12px rgba(255, 152, 0, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(255, 152, 0, 0.2)" 86 | } 87 | }, 88 | danger: { 89 | backgroundColor: dangerColor, 90 | boxShadow: 91 | "0 2px 2px 0 rgba(244, 67, 54, 0.14), 0 3px 1px -2px rgba(244, 67, 54, 0.2), 0 1px 5px 0 rgba(244, 67, 54, 0.12)", 92 | "&:hover": { 93 | backgroundColor: dangerColor, 94 | boxShadow: 95 | "0 14px 26px -12px rgba(244, 67, 54, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(244, 67, 54, 0.2)" 96 | } 97 | }, 98 | rose: { 99 | backgroundColor: roseColor, 100 | boxShadow: 101 | "0 2px 2px 0 rgba(233, 30, 99, 0.14), 0 3px 1px -2px rgba(233, 30, 99, 0.2), 0 1px 5px 0 rgba(233, 30, 99, 0.12)", 102 | "&:hover": { 103 | backgroundColor: roseColor, 104 | boxShadow: 105 | "0 14px 26px -12px rgba(233, 30, 99, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(233, 30, 99, 0.2)" 106 | } 107 | }, 108 | white: { 109 | "&,&:focus,&:hover": { 110 | backgroundColor: "#FFFFFF", 111 | color: grayColor 112 | } 113 | }, 114 | simple: { 115 | "&,&:focus,&:hover": { 116 | color: "#FFFFFF", 117 | background: "transparent", 118 | boxShadow: "none" 119 | } 120 | }, 121 | transparent: { 122 | "&,&:focus,&:hover": { 123 | color: "inherit", 124 | background: "transparent", 125 | boxShadow: "none" 126 | } 127 | }, 128 | round: { 129 | borderRadius: "30px" 130 | }, 131 | disabled: { 132 | opacity: "0.65", 133 | pointerEvents: "none" 134 | } 135 | }; 136 | 137 | export default buttonStyle; 138 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react.jsx: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | ========================================================= 4 | * Material Dashboard React - v1.2.0 based on Material Dashboard - v1.2.0 5 | ========================================================= 6 | 7 | * Product Page: http://www.creative-tim.com/product/material-dashboard-react 8 | * Copyright 2018 Creative Tim (http://www.creative-tim.com) 9 | * Licensed under MIT (https://github.com/creativetimofficial/material-dashboard-react/blob/master/LICENSE.md) 10 | 11 | ========================================================= 12 | 13 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | */ 16 | 17 | // ############################## 18 | // // // Variables - Styles that are used on more than one component 19 | // ############################# 20 | 21 | const drawerWidth = 260; 22 | 23 | const transition = { 24 | transition: "all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1)" 25 | }; 26 | 27 | const container = { 28 | paddingRight: "15px", 29 | paddingLeft: "15px", 30 | marginRight: "auto", 31 | marginLeft: "auto" 32 | }; 33 | 34 | const boxShadow = { 35 | boxShadow: 36 | "0 10px 30px -12px rgba(0, 0, 0, 0.42), 0 4px 25px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.2)" 37 | }; 38 | 39 | const card = { 40 | display: "inline-block", 41 | position: "relative", 42 | width: "100%", 43 | margin: "25px 0", 44 | boxShadow: "0 1px 4px 0 rgba(0, 0, 0, 0.14)", 45 | borderRadius: "3px", 46 | color: "rgba(0, 0, 0, 0.87)", 47 | background: "#fff" 48 | }; 49 | 50 | const defaultFont = { 51 | fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', 52 | fontWeight: "300", 53 | lineHeight: "1.5em" 54 | }; 55 | 56 | const primaryColor = "#9c27b0"; 57 | const warningColor = "#ff9800"; 58 | const dangerColor = "#f44336"; 59 | const successColor = "#4caf50"; 60 | const infoColor = "#00acc1"; 61 | const roseColor = "#e91e63"; 62 | const grayColor = "#999999"; 63 | 64 | const primaryBoxShadow = { 65 | boxShadow: 66 | "0 12px 20px -10px rgba(156, 39, 176, 0.28), 0 4px 20px 0px rgba(0, 0, 0, 0.12), 0 7px 8px -5px rgba(156, 39, 176, 0.2)" 67 | }; 68 | const infoBoxShadow = { 69 | boxShadow: 70 | "0 12px 20px -10px rgba(0, 188, 212, 0.28), 0 4px 20px 0px rgba(0, 0, 0, 0.12), 0 7px 8px -5px rgba(0, 188, 212, 0.2)" 71 | }; 72 | const successBoxShadow = { 73 | boxShadow: 74 | "0 12px 20px -10px rgba(76, 175, 80, 0.28), 0 4px 20px 0px rgba(0, 0, 0, 0.12), 0 7px 8px -5px rgba(76, 175, 80, 0.2)" 75 | }; 76 | const warningBoxShadow = { 77 | boxShadow: 78 | "0 12px 20px -10px rgba(255, 152, 0, 0.28), 0 4px 20px 0px rgba(0, 0, 0, 0.12), 0 7px 8px -5px rgba(255, 152, 0, 0.2)" 79 | }; 80 | const dangerBoxShadow = { 81 | boxShadow: 82 | "0 12px 20px -10px rgba(244, 67, 54, 0.28), 0 4px 20px 0px rgba(0, 0, 0, 0.12), 0 7px 8px -5px rgba(244, 67, 54, 0.2)" 83 | }; 84 | 85 | const orangeCardHeader = { 86 | background: "linear-gradient(60deg, #ffa726, #fb8c00)", 87 | ...warningBoxShadow 88 | }; 89 | const greenCardHeader = { 90 | background: "linear-gradient(60deg, #66bb6a, #43a047)", 91 | ...successBoxShadow 92 | }; 93 | const redCardHeader = { 94 | background: "linear-gradient(60deg, #ef5350, #e53935)", 95 | ...dangerBoxShadow 96 | }; 97 | const blueCardHeader = { 98 | background: "linear-gradient(60deg, #26c6da, #00acc1)", 99 | ...infoBoxShadow 100 | }; 101 | const purpleCardHeader = { 102 | background: "linear-gradient(60deg, #ab47bc, #8e24aa)", 103 | ...primaryBoxShadow 104 | }; 105 | 106 | const cardActions = { 107 | margin: "0 20px 10px", 108 | paddingTop: "10px", 109 | borderTop: "1px solid #eeeeee", 110 | height: "auto", 111 | ...defaultFont 112 | }; 113 | 114 | const cardHeader = { 115 | margin: "-20px 15px 0", 116 | borderRadius: "3px", 117 | padding: "15px" 118 | }; 119 | 120 | const defaultBoxShadow = { 121 | border: "0", 122 | borderRadius: "3px", 123 | boxShadow: 124 | "0 10px 20px -12px rgba(0, 0, 0, 0.42), 0 3px 20px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.2)", 125 | padding: "10px 0", 126 | transition: "all 150ms ease 0s" 127 | }; 128 | 129 | export { 130 | //variables 131 | drawerWidth, 132 | transition, 133 | container, 134 | boxShadow, 135 | card, 136 | defaultFont, 137 | primaryColor, 138 | warningColor, 139 | dangerColor, 140 | successColor, 141 | infoColor, 142 | roseColor, 143 | grayColor, 144 | primaryBoxShadow, 145 | infoBoxShadow, 146 | successBoxShadow, 147 | warningBoxShadow, 148 | dangerBoxShadow, 149 | orangeCardHeader, 150 | greenCardHeader, 151 | redCardHeader, 152 | blueCardHeader, 153 | purpleCardHeader, 154 | cardActions, 155 | cardHeader, 156 | defaultBoxShadow 157 | }; 158 | -------------------------------------------------------------------------------- /src/web/src/variables/charts.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // javascript library for creating charts 3 | // ############################# 4 | var Chartist = require("chartist"); 5 | 6 | // ############################## 7 | // // // variables used to create animation on charts 8 | // ############################# 9 | var delays = 80, 10 | durations = 500; 11 | var delays2 = 80, 12 | durations2 = 500; 13 | 14 | // ############################## 15 | // // // Daily Sales 16 | // ############################# 17 | 18 | const dailySalesChart = { 19 | data: { 20 | labels: ["M", "T", "W", "T", "F", "S", "S"], 21 | series: [[12, 17, 7, 17, 23, 18, 38]] 22 | }, 23 | options: { 24 | lineSmooth: Chartist.Interpolation.cardinal({ 25 | tension: 0 26 | }), 27 | low: 0, 28 | high: 50, // creative tim: we recommend you to set the high sa the biggest value + something for a better look 29 | chartPadding: { 30 | top: 0, 31 | right: 0, 32 | bottom: 0, 33 | left: 0 34 | } 35 | }, 36 | // for animation 37 | animation: { 38 | draw: function(data) { 39 | if (data.type === "line" || data.type === "area") { 40 | data.element.animate({ 41 | d: { 42 | begin: 600, 43 | dur: 700, 44 | from: data.path 45 | .clone() 46 | .scale(1, 0) 47 | .translate(0, data.chartRect.height()) 48 | .stringify(), 49 | to: data.path.clone().stringify(), 50 | easing: Chartist.Svg.Easing.easeOutQuint 51 | } 52 | }); 53 | } else if (data.type === "point") { 54 | data.element.animate({ 55 | opacity: { 56 | begin: (data.index + 1) * delays, 57 | dur: durations, 58 | from: 0, 59 | to: 1, 60 | easing: "ease" 61 | } 62 | }); 63 | } 64 | } 65 | } 66 | }; 67 | 68 | // ############################## 69 | // // // Email Subscriptions 70 | // ############################# 71 | 72 | const emailsSubscriptionChart = { 73 | data: { 74 | labels: [ 75 | "Jan", 76 | "Feb", 77 | "Mar", 78 | "Apr", 79 | "Mai", 80 | "Jun", 81 | "Jul", 82 | "Aug", 83 | "Sep", 84 | "Oct", 85 | "Nov", 86 | "Dec" 87 | ], 88 | series: [[542, 443, 320, 780, 553, 453, 326, 434, 568, 610, 756, 895]] 89 | }, 90 | options: { 91 | axisX: { 92 | showGrid: false 93 | }, 94 | low: 0, 95 | high: 1000, 96 | chartPadding: { 97 | top: 0, 98 | right: 5, 99 | bottom: 0, 100 | left: 0 101 | } 102 | }, 103 | responsiveOptions: [ 104 | [ 105 | "screen and (max-width: 640px)", 106 | { 107 | seriesBarDistance: 5, 108 | axisX: { 109 | labelInterpolationFnc: function(value) { 110 | return value[0]; 111 | } 112 | } 113 | } 114 | ] 115 | ], 116 | animation: { 117 | draw: function(data) { 118 | if (data.type === "bar") { 119 | data.element.animate({ 120 | opacity: { 121 | begin: (data.index + 1) * delays2, 122 | dur: durations2, 123 | from: 0, 124 | to: 1, 125 | easing: "ease" 126 | } 127 | }); 128 | } 129 | } 130 | } 131 | }; 132 | 133 | // ############################## 134 | // // // Completed Tasks 135 | // ############################# 136 | 137 | const completedTasksChart = { 138 | data: { 139 | labels: ["12am", "3pm", "6pm", "9pm", "12pm", "3am", "6am", "9am"], 140 | series: [[230, 750, 450, 300, 280, 240, 200, 190]] 141 | }, 142 | options: { 143 | lineSmooth: Chartist.Interpolation.cardinal({ 144 | tension: 0 145 | }), 146 | low: 0, 147 | high: 1000, // creative tim: we recommend you to set the high sa the biggest value + something for a better look 148 | chartPadding: { 149 | top: 0, 150 | right: 0, 151 | bottom: 0, 152 | left: 0 153 | } 154 | }, 155 | animation: { 156 | draw: function(data) { 157 | if (data.type === "line" || data.type === "area") { 158 | data.element.animate({ 159 | d: { 160 | begin: 600, 161 | dur: 700, 162 | from: data.path 163 | .clone() 164 | .scale(1, 0) 165 | .translate(0, data.chartRect.height()) 166 | .stringify(), 167 | to: data.path.clone().stringify(), 168 | easing: Chartist.Svg.Easing.easeOutQuint 169 | } 170 | }); 171 | } else if (data.type === "point") { 172 | data.element.animate({ 173 | opacity: { 174 | begin: (data.index + 1) * delays, 175 | dur: durations, 176 | from: 0, 177 | to: 1, 178 | easing: "ease" 179 | } 180 | }); 181 | } 182 | } 183 | } 184 | }; 185 | 186 | module.exports = { 187 | dailySalesChart, 188 | emailsSubscriptionChart, 189 | completedTasksChart 190 | }; 191 | -------------------------------------------------------------------------------- /src/web/src/assets/css/material-dashboard-react.css: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | ========================================================= 4 | * Material Dashboard React - v1.2.0 based on Material Dashboard - v1.2.0 5 | ========================================================= 6 | 7 | * Product Page: http://www.creative-tim.com/product/material-dashboard-react 8 | * Copyright 2018 Creative Tim (http://www.creative-tim.com) 9 | * Licensed under MIT (https://github.com/creativetimofficial/material-dashboard-react/blob/master/LICENSE.md) 10 | 11 | ========================================================= 12 | 13 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | */ 16 | .ct-grid { 17 | stroke: rgba(255, 255, 255, 0.2); 18 | stroke-width: 1px; 19 | stroke-dasharray: 2px; 20 | } 21 | 22 | .ct-series-a .ct-point, .ct-series-a .ct-line, .ct-series-a .ct-bar, .ct-series-a .ct-slice-donut { 23 | stroke: rgba(255, 255, 255, 0.8); 24 | } 25 | 26 | .ct-label.ct-horizontal.ct-end { 27 | -webkit-box-align: flex-start; 28 | -webkit-align-items: flex-start; 29 | -ms-flex-align: flex-start; 30 | align-items: flex-start; 31 | -webkit-box-pack: flex-start; 32 | -webkit-justify-content: flex-start; 33 | -ms-flex-pack: flex-start; 34 | justify-content: flex-start; 35 | text-align: left; 36 | text-anchor: start; 37 | } 38 | 39 | .ct-label { 40 | color: rgba(255, 255, 255, 0.7); 41 | } 42 | 43 | .ct-chart-line .ct-label, .ct-chart-bar .ct-label { 44 | display: block; 45 | display: -webkit-box; 46 | display: -moz-box; 47 | display: -ms-flexbox; 48 | display: -webkit-flex; 49 | display: flex; 50 | } 51 | 52 | .ct-label { 53 | fill: rgba(0, 0, 0, 0.4); 54 | line-height: 1; 55 | } 56 | html * { 57 | -webkit-font-smoothing: antialiased; 58 | -moz-osx-font-smoothing: grayscale; 59 | } 60 | body { 61 | background-color: #EEEEEE; 62 | color: #3C4858; 63 | margin: 0; 64 | font-family: Roboto, Helvetica, Arial, sans-serif; 65 | font-weight: 300; 66 | line-height: 1.5em; 67 | } 68 | 69 | blockquote footer:before, blockquote small:before { 70 | content: '\2014 \00A0'; 71 | } 72 | 73 | h1 { 74 | font-size: 3em; 75 | line-height: 1.15em; 76 | } 77 | 78 | h2 { 79 | font-size: 2.4em; 80 | } 81 | 82 | h3 { 83 | font-size: 1.825em; 84 | line-height: 1.4em; 85 | margin: 20px 0 10px; 86 | } 87 | 88 | h4 { 89 | font-size: 1.3em; 90 | line-height: 1.4em; 91 | } 92 | 93 | h5 { 94 | font-size: 1.25em; 95 | line-height: 1.4em; 96 | margin-bottom: 15px; 97 | } 98 | 99 | h6 { 100 | font-size: 1em; 101 | text-transform: uppercase; 102 | font-weight: 500; 103 | } 104 | 105 | body { 106 | background-color: #EEEEEE; 107 | color: #3C4858; 108 | } 109 | 110 | blockquote p { 111 | font-style: italic; 112 | } 113 | 114 | body, h1, h2, h3, h4, h5, h6 { 115 | font-family: "Roboto", "Helvetica", "Arial", sans-serif; 116 | font-weight: 300; 117 | line-height: 1.5em; 118 | } 119 | 120 | a { 121 | color: #9c27b0; 122 | text-decoration: none; 123 | } 124 | 125 | a:hover, a:focus { 126 | color: #89229b; 127 | text-decoration: none; 128 | } 129 | 130 | legend { 131 | border-bottom: 0; 132 | } 133 | 134 | * { 135 | -webkit-tap-highlight-color: rgba(255, 255, 255, 0); 136 | -webkit-tap-highlight-color: transparent; 137 | } 138 | 139 | *:focus { 140 | outline: 0; 141 | } 142 | 143 | a:focus, a:active, 144 | button:active, button:focus, button:hover, 145 | button::-moz-focus-inner, 146 | input[type="reset"]::-moz-focus-inner, 147 | input[type="button"]::-moz-focus-inner, 148 | input[type="submit"]::-moz-focus-inner, 149 | select::-moz-focus-inner, 150 | input[type="file"] > input[type="button"]::-moz-focus-inner { 151 | outline: 0 !important; 152 | } 153 | 154 | legend { 155 | margin-bottom: 20px; 156 | font-size: 21px; 157 | } 158 | 159 | output { 160 | padding-top: 8px; 161 | font-size: 14px; 162 | line-height: 1.42857; 163 | } 164 | 165 | label { 166 | font-size: 14px; 167 | line-height: 1.42857; 168 | color: #AAAAAA; 169 | font-weight: 400; 170 | } 171 | 172 | footer { 173 | padding: 15px 0; 174 | } 175 | 176 | footer ul { 177 | margin-bottom: 0; 178 | padding: 0; 179 | list-style: none; 180 | } 181 | 182 | footer ul li { 183 | display: inline-block; 184 | } 185 | 186 | footer ul li a { 187 | color: inherit; 188 | padding: 15px; 189 | font-weight: 500; 190 | font-size: 12px; 191 | text-transform: uppercase; 192 | border-radius: 3px; 193 | text-decoration: none; 194 | position: relative; 195 | display: block; 196 | } 197 | 198 | footer ul li a:hover { 199 | text-decoration: none; 200 | } 201 | 202 | @media (max-width: 991px) { 203 | body, 204 | html { 205 | position: relative; 206 | overflow-x: hidden; 207 | } 208 | 209 | #bodyClick { 210 | height: 100%; 211 | width: 100%; 212 | position: fixed; 213 | opacity: 0; 214 | top: 0; 215 | left: auto; 216 | right: 260px; 217 | content: ""; 218 | z-index: 9999; 219 | overflow-x: hidden; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/web/src/layouts/Dashboard/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | //try native implementation instead of this helper lib 3 | //import EventSource from "../../eventsource.js"; 4 | import PropTypes from "prop-types"; 5 | import { Switch, Route, Redirect } from "react-router-dom"; 6 | import PerfectScrollbar from "perfect-scrollbar"; 7 | import "perfect-scrollbar/css/perfect-scrollbar.css"; 8 | import { withStyles } from "material-ui"; 9 | 10 | import { Header, Footer, Sidebar } from "components"; 11 | 12 | import dashboardRoutes from "routes/dashboard.jsx"; 13 | 14 | import appStyle from "assets/jss/material-dashboard-react/appStyle.jsx"; 15 | 16 | import image from "assets/img/sidebar-2.jpg"; 17 | import logo from "assets/img/reactlogo.png"; 18 | 19 | class App extends React.Component { 20 | constructor() { 21 | super(); 22 | if (this.supportsSSE()) { 23 | this.state = {alerts:[]}; 24 | } else { 25 | this.state = {alerts:["Browser does not support EventSource :("]}; 26 | } 27 | } 28 | 29 | state = { 30 | mobileOpen: false, 31 | alerts: [], 32 | sensors: {}, 33 | miners: {} 34 | }; 35 | 36 | supportsSSE() { 37 | return !!window.EventSource; 38 | } 39 | 40 | handleDrawerToggle = () => { 41 | this.setState({ mobileOpen: !this.state.mobileOpen }); 42 | }; 43 | 44 | ismaps() { 45 | return this.props.location.pathname === "/maps"; 46 | } 47 | 48 | isnotifications() { 49 | return this.props.location.pathname === "/notifications"; 50 | } 51 | 52 | componentDidMount() { 53 | if(navigator.platform.indexOf("Win") > -1){ 54 | // eslint-disable-next-line 55 | const ps = new PerfectScrollbar(this.refs.mainPanel); 56 | } 57 | if (this.supportsSSE() && !this.eventListener) { 58 | this.eventListener = new EventSource("/sse"); 59 | this.subscribe(this.eventListener); 60 | } 61 | } 62 | 63 | componentDidUpdate() { 64 | this.refs.mainPanel.scrollTop = 0; 65 | } 66 | 67 | componentWillUnmount() { 68 | if (this.eventListener) { this.eventListener.close(); } 69 | } 70 | 71 | addAlert(alert) { 72 | //limits alerts to 1000 messages. 73 | let txt = alert; 74 | if (txt.indexOf(":") < 0) 75 | { 76 | const d = new Date(); 77 | txt = d.toLocaleString() + ":" + txt; 78 | } 79 | //console.log(txt); 80 | this.setState({ 81 | alerts: [txt, ...this.state.alerts.slice(0, 999)] 82 | }); 83 | } 84 | 85 | subscribe(es) { 86 | const that = this; 87 | if (!es) { return; } 88 | es.addEventListener("full-cycle-alert", (e) => { 89 | that.addAlert(e.data); 90 | }, false); 91 | 92 | es.addEventListener("open", (e) => { 93 | //var d = new Date(); 94 | //let txt = d.toLocaleString() + ": EventSource opened"; 95 | //console.log(txt); 96 | }, false); 97 | 98 | es.addEventListener("error", (e) => { 99 | var d = new Date(); 100 | let txt = d.toLocaleString() + ": "; 101 | switch (e.readyState) { 102 | case EventSource.CONNECTING: 103 | txt += "EventSource reconnecting..."; 104 | break; 105 | case EventSource.CLOSED: 106 | txt += "EventSource failed. Will not retry."; 107 | break; 108 | default: 109 | txt += "EventSource failed. unknown readyState " + e.readyState; 110 | } 111 | console.log(txt); 112 | 113 | }, false); 114 | 115 | } 116 | 117 | switchRoutes= () => { 118 | const that = this; 119 | return ( 120 | 121 | {dashboardRoutes.map((prop, key) => { 122 | if (prop.redirect) 123 | return ; 124 | 125 | var RoutedComponent = prop.component; 126 | return } 128 | />; 129 | })} 130 | 131 | );}; 132 | 133 | render() { 134 | const { classes, ...rest } = this.props; 135 | const routeswitches = this.switchRoutes(); 136 | return ( 137 |
    138 | 148 |
    149 |
    155 | {/* custom layouts */} 156 | {!this.ismaps() ? ( 157 |
    158 |
    {routeswitches}
    159 |
    160 | ) : ( 161 |
    {routeswitches}
    162 | )} 163 | {!this.ismaps() ?
    : null} 164 |
    165 |
    166 | ); 167 | } 168 | } 169 | 170 | App.propTypes = { 171 | classes: PropTypes.object.isRequired 172 | }; 173 | 174 | export default withStyles(appStyle)(App); 175 | -------------------------------------------------------------------------------- /src/web/src/views/UserProfile/UserProfile.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid, InputLabel } from "material-ui"; 3 | 4 | import { 5 | ProfileCard, 6 | RegularCard, 7 | Button, 8 | CustomInput, 9 | ItemGrid 10 | } from "components"; 11 | 12 | import avatar from "assets/img/faces/marc.jpg"; 13 | 14 | function UserProfile({ ...props }) { 15 | return ( 16 |
    17 | 18 | 19 | 24 | 25 | 26 | 36 | 37 | 38 | 45 | 46 | 47 | 54 | 55 | 56 | 57 | 58 | 65 | 66 | 67 | 74 | 75 | 76 | 77 | 78 | 85 | 86 | 87 | 94 | 95 | 96 | 103 | 104 | 105 | 106 | 107 | 108 | About me 109 | 110 | 121 | 122 | 123 |
    124 | } 125 | footer={} 126 | /> 127 | 128 | 129 | 136 | Follow 137 | 138 | } 139 | /> 140 | 141 | 142 |
    143 | ); 144 | } 145 | 146 | export default UserProfile; 147 | -------------------------------------------------------------------------------- /src/web/src/views/Typography/Typography.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles } from "material-ui"; 3 | 4 | import { 5 | P, 6 | Quote, 7 | Muted, 8 | Primary, 9 | Info, 10 | Success, 11 | Warning, 12 | Danger, 13 | Small, 14 | RegularCard 15 | } from "components"; 16 | 17 | const style = { 18 | typo: { 19 | paddingLeft: "25%", 20 | marginBottom: "40px", 21 | position: "relative" 22 | }, 23 | note: { 24 | fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', 25 | bottom: "10px", 26 | color: "#c0c1c2", 27 | display: "block", 28 | fontWeight: "400", 29 | fontSize: "13px", 30 | lineHeight: "13px", 31 | left: "0", 32 | marginLeft: "20px", 33 | position: "absolute", 34 | width: "260px" 35 | } 36 | }; 37 | function TypographyPage({ ...props }) { 38 | return ( 39 | 44 |
    45 |
    Header 1
    46 |

    The Life of Material Dashboard

    47 |
    48 |
    49 |
    Header 2
    50 |

    The Life of Material Dashboard

    51 |
    52 |
    53 |
    Header 3
    54 |

    The Life of Material Dashboard

    55 |
    56 |
    57 |
    Header 4
    58 |

    The Life of Material Dashboard

    59 |
    60 |
    61 |
    Header 5
    62 |
    The Life of Material Dashboard
    63 |
    64 |
    65 |
    Header 6
    66 |
    The Life of Material Dashboard
    67 |
    68 |
    69 |
    Paragraph
    70 |

    71 | I will be the leader of a company that ends up being worth 72 | billions of dollars, because I got the answers. I understand 73 | culture. I am the nucleus. I think that’s a responsibility that I 74 | have, to push possibilities, to show people, this is the level 75 | that things could be at. 76 |

    77 |
    78 |
    79 |
    Quote
    80 | 84 |
    85 |
    86 |
    Muted Text
    87 | 88 | I will be the leader of a company that ends up being worth 89 | billions of dollars, because I got the answers... 90 | 91 |
    92 |
    93 |
    Primary Text
    94 | 95 | I will be the leader of a company that ends up being worth 96 | billions of dollars, because I got the answers... 97 | 98 |
    99 |
    100 |
    Info Text
    101 | 102 | I will be the leader of a company that ends up being worth 103 | billions of dollars, because I got the answers... 104 | 105 |
    106 |
    107 |
    Success Text
    108 | 109 | I will be the leader of a company that ends up being worth 110 | billions of dollars, because I got the answers... 111 | 112 |
    113 |
    114 |
    Warning Text
    115 | 116 | I will be the leader of a company that ends up being worth 117 | billions of dollars, because I got the answers... 118 | 119 |
    120 |
    121 |
    Danger Text
    122 | 123 | I will be the leader of a company that ends up being worth 124 | billions of dollars, because I got the answers... 125 | 126 |
    127 |
    128 |
    Small Tag
    129 |

    130 | Header with small subtitle
    131 | Use "Small" tag for the headers 132 |

    133 |
    134 |
    135 | } 136 | /> 137 | ); 138 | } 139 | 140 | export default withStyles(style)(TypographyPage); 141 | -------------------------------------------------------------------------------- /src/web/src/assets/jss/material-dashboard-react/sidebarStyle.jsx: -------------------------------------------------------------------------------- 1 | // ############################## 2 | // // // Sidebar styles 3 | // ############################# 4 | 5 | import { 6 | drawerWidth, 7 | transition, 8 | boxShadow, 9 | defaultFont, 10 | primaryColor, 11 | primaryBoxShadow, 12 | infoColor, 13 | successColor, 14 | warningColor, 15 | dangerColor 16 | } from "assets/jss/material-dashboard-react.jsx"; 17 | 18 | const sidebarStyle = theme => ({ 19 | drawerPaper: { 20 | border: "none", 21 | position: "fixed", 22 | top: "0", 23 | bottom: "0", 24 | left: "0", 25 | zIndex: "1", 26 | // overflow: "auto", 27 | ...boxShadow, 28 | width: drawerWidth, 29 | [theme.breakpoints.up("md")]: { 30 | width: drawerWidth, 31 | position: "fixed", 32 | height: "100%" 33 | }, 34 | [theme.breakpoints.down("sm")]: { 35 | width: drawerWidth, 36 | ...boxShadow, 37 | position: "fixed", 38 | display: "block", 39 | top: "0", 40 | height: "100vh", 41 | right: "0", 42 | left: "auto", 43 | zIndex: "1032", 44 | visibility: "visible", 45 | overflowY: "visible", 46 | borderTop: "none", 47 | textAlign: "left", 48 | paddingRight: "0px", 49 | paddingLeft: "0", 50 | transform: `translate3d(${drawerWidth}px, 0, 0)`, 51 | ...transition 52 | } 53 | }, 54 | logo: { 55 | position: "relative", 56 | padding: "15px 15px", 57 | zIndex: "4", 58 | "&:after": { 59 | content: '""', 60 | position: "absolute", 61 | bottom: "0", 62 | 63 | height: "1px", 64 | right: "15px", 65 | width: "calc(100% - 30px)", 66 | backgroundColor: "rgba(180, 180, 180, 0.3)" 67 | } 68 | }, 69 | logoLink: { 70 | ...defaultFont, 71 | textTransform: "uppercase", 72 | padding: "5px 0", 73 | display: "block", 74 | fontSize: "18px", 75 | textAlign: "left", 76 | fontWeight: "400", 77 | lineHeight: "30px", 78 | textDecoration: "none", 79 | backgroundColor: "transparent", 80 | "&,&:hover": { 81 | color: "#FFFFFF" 82 | } 83 | }, 84 | logoImage: { 85 | width: "30px", 86 | display: "inline-block", 87 | maxHeight: "30px", 88 | marginLeft: "10px", 89 | marginRight: "15px" 90 | }, 91 | img: { 92 | width: "35px", 93 | top: "22px", 94 | position: "absolute", 95 | verticalAlign: "middle", 96 | border: "0" 97 | }, 98 | background: { 99 | position: "absolute", 100 | zIndex: "1", 101 | height: "100%", 102 | width: "100%", 103 | display: "block", 104 | top: "0", 105 | left: "0", 106 | backgroundSize: "cover", 107 | backgroundPosition: "center center", 108 | "&:after": { 109 | position: "absolute", 110 | zIndex: "3", 111 | width: "100%", 112 | height: "100%", 113 | content: '""', 114 | display: "block", 115 | background: "#000", 116 | opacity: ".8" 117 | } 118 | }, 119 | list: { 120 | marginTop: "20px", 121 | paddingLeft: "0", 122 | paddingTop: "0", 123 | paddingBottom: "0", 124 | marginBottom: "0", 125 | listStyle: "none" 126 | }, 127 | item: { 128 | position: "relative", 129 | display: "block", 130 | textDecoration: "none", 131 | }, 132 | itemLink: { 133 | width: "auto", 134 | transition: "all 300ms linear", 135 | margin: "10px 15px 0", 136 | borderRadius: "3px", 137 | position: "relative", 138 | display: "block", 139 | padding: "10px 15px", 140 | backgroundColor: "transparent", 141 | ...defaultFont 142 | }, 143 | itemIcon: { 144 | width: "24px", 145 | height: "30px", 146 | float: "left", 147 | marginRight: "15px", 148 | textAlign: "center", 149 | verticalAlign: "middle", 150 | color: "rgba(255, 255, 255, 0.8)" 151 | }, 152 | itemText: { 153 | ...defaultFont, 154 | margin: "0", 155 | lineHeight: "30px", 156 | fontSize: "14px", 157 | color: "#FFFFFF" 158 | }, 159 | whiteFont: { 160 | color: "#FFFFFF" 161 | }, 162 | purple: { 163 | backgroundColor: primaryColor, 164 | ...primaryBoxShadow, 165 | "&:hover": { 166 | backgroundColor: primaryColor, 167 | ...primaryBoxShadow 168 | } 169 | }, 170 | blue: { 171 | backgroundColor: infoColor, 172 | boxShadow: 173 | "0 12px 20px -10px rgba(0,188,212,.28), 0 4px 20px 0 rgba(0,0,0,.12), 0 7px 8px -5px rgba(0,188,212,.2)", 174 | "&:hover": { 175 | backgroundColor: infoColor, 176 | boxShadow: 177 | "0 12px 20px -10px rgba(0,188,212,.28), 0 4px 20px 0 rgba(0,0,0,.12), 0 7px 8px -5px rgba(0,188,212,.2)" 178 | } 179 | }, 180 | green: { 181 | backgroundColor: successColor, 182 | boxShadow: 183 | "0 12px 20px -10px rgba(76,175,80,.28), 0 4px 20px 0 rgba(0,0,0,.12), 0 7px 8px -5px rgba(76,175,80,.2)", 184 | "&:hover": { 185 | backgroundColor: successColor, 186 | boxShadow: 187 | "0 12px 20px -10px rgba(76,175,80,.28), 0 4px 20px 0 rgba(0,0,0,.12), 0 7px 8px -5px rgba(76,175,80,.2)" 188 | } 189 | }, 190 | orange: { 191 | backgroundColor: warningColor, 192 | boxShadow: 193 | "0 12px 20px -10px rgba(255,152,0,.28), 0 4px 20px 0 rgba(0,0,0,.12), 0 7px 8px -5px rgba(255,152,0,.2)", 194 | "&:hover": { 195 | backgroundColor: warningColor, 196 | boxShadow: 197 | "0 12px 20px -10px rgba(255,152,0,.28), 0 4px 20px 0 rgba(0,0,0,.12), 0 7px 8px -5px rgba(255,152,0,.2)" 198 | } 199 | }, 200 | red: { 201 | backgroundColor: dangerColor, 202 | boxShadow: 203 | "0 12px 20px -10px rgba(244,67,54,.28), 0 4px 20px 0 rgba(0,0,0,.12), 0 7px 8px -5px rgba(244,67,54,.2)", 204 | "&:hover": { 205 | backgroundColor: dangerColor, 206 | boxShadow: 207 | "0 12px 20px -10px rgba(244,67,54,.28), 0 4px 20px 0 rgba(0,0,0,.12), 0 7px 8px -5px rgba(244,67,54,.2)" 208 | } 209 | }, 210 | sidebarWrapper: { 211 | position: "relative", 212 | height: "calc(100vh - 75px)", 213 | overflow: "auto", 214 | width: "260px", 215 | zIndex: "4", 216 | overflowScrolling: "touch" 217 | } 218 | }); 219 | 220 | export default sidebarStyle; 221 | -------------------------------------------------------------------------------- /src/api/src/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const express = require("express"); 4 | const serveStatic = require("serve-static"); 5 | const path = require("path"); 6 | const SSE = require("sse"); 7 | const redis = require("redis"); 8 | const amqp = require("amqplib/callback_api"); 9 | 10 | const app = express(); 11 | 12 | // const passport = require('passport') 13 | // const BasicStrategy = require('passport-http').BasicStrategy 14 | // passport.use(new BasicStrategy( 15 | // function(username, password, done) { 16 | // //todo: obviously needs to change 17 | // if (username.valueOf() === "fullcycle" && password.valueOf() === "mining") 18 | // return done(null, true); 19 | // else 20 | // { 21 | // console.log("rejected!") 22 | // return done(null, false); 23 | // } 24 | // } 25 | // )); 26 | 27 | const services = require("./services"); 28 | const messages = require("./messages"); 29 | 30 | var api = require("./api.js"); 31 | 32 | function bail(err, conn) { 33 | console.error("bailing..."); 34 | console.error(err); 35 | if (conn) conn.close(function() { 36 | // if (doexit) 37 | // process.exit(1); 38 | }); 39 | } 40 | 41 | 42 | //route all other calls to the home page. this is causing "path is not defined" in line 179 43 | // app.get("/*", function(req, res) { 44 | // res.sendFile(path.join(__dirname, "/index.html"), function(err) { 45 | // if (err) { 46 | // res.status(500).send(err) 47 | // } 48 | // }) 49 | // }); 50 | 51 | //in production this serves up the react bundle 52 | app.use(serveStatic("../web/build") 53 | //, 54 | // passport.authenticate("basic", { session: false }) 55 | ); 56 | app.use("/api", api); 57 | var server = app.listen(services.web.port, () => console.log(`Listening on port ${services.web.port}`)); 58 | function onWebError(error) { 59 | if (error.syscall !== "listen") { 60 | throw error; 61 | } 62 | 63 | //var bind = typeof port === "string" ? "Pipe " + port : "Port " + port; 64 | var bind = services.web.port; 65 | 66 | // handle specific listen errors with friendly messages 67 | switch (error.code) { 68 | case "EACCES": 69 | console.error(error.code + ":" + bind + " requires elevated privileges"); 70 | process.exit(1); 71 | break; 72 | case "EADDRINUSE": 73 | console.error(error.code + ":" + bind + " is already in use. Close the other app and try again"); 74 | process.exit(1); 75 | break; 76 | default: 77 | throw error; 78 | } 79 | } 80 | server.on("error", onWebError); 81 | 82 | var busConnect = null; 83 | 84 | function on_connect(err, conn) { 85 | if (err !== null) return bail(err); 86 | process.once("SIGINT", function() { conn.close(); }); 87 | 88 | busConnect = conn; 89 | 90 | } 91 | 92 | //set up the full cycle alerts feed to send alerts to the browser 93 | var sse = new SSE(server); 94 | sse.on("connection", function (sseConnection) { 95 | //console.log("new sse connection"); 96 | 97 | const qAlert = "alert"; 98 | let alertChannel = null; 99 | 100 | function alertMessage(msg) { 101 | if (msg) { 102 | //msg.content.toString() 103 | //console.log(" [x] '%s'", "received alert message"); 104 | sseConnection.send({ 105 | event: "full-cycle-alert", 106 | data: msg.content.toString() 107 | }); 108 | } 109 | } 110 | 111 | function on_channel_open_alert(err, ch) { 112 | if (err !== null) { return bail(err, busConnect); } 113 | alertChannel = ch; 114 | ch.on("error", function (err) { 115 | //console.error(err); 116 | //console.log("channel Closed"); 117 | }); 118 | ch.assertQueue("", {exclusive: true}, function(err, ok) { 119 | var q = ok.queue; 120 | ch.bindQueue(q, qAlert, ""); 121 | ch.consume(q, alertMessage, {noAck: true}, function(err, ok) { 122 | if (err !== null) return bail(err, busConnect); 123 | //console.log(" [*] Waiting for alert. To exit press CTRL+C."); 124 | }); 125 | }); 126 | } 127 | 128 | const qMiner = "statisticsupdated"; 129 | let miner_channel = null; 130 | 131 | function minerMessage(msg) { 132 | if (msg) { 133 | //msg.content.toString() 134 | //console.log(" [x] '%s'", "received miner message"); 135 | sseConnection.send({ 136 | event: "full-cycle-miner", 137 | data: msg.content.toString() 138 | }); 139 | } 140 | } 141 | 142 | function on_channel_open_miner(err, ch) { 143 | if (err !== null) return bail(err, busConnect); 144 | miner_channel = ch; 145 | ch.on("error", function (err) { 146 | console.error(err); 147 | //console.log("miner channel Closed"); 148 | }); 149 | ch.assertQueue("", {exclusive: true}, function(err, ok) { 150 | var q = ok.queue; 151 | ch.bindQueue(q, qMiner, ""); 152 | ch.consume(q, minerMessage, {noAck: true}, function(err, ok) { 153 | if (err !== null) return bail(err, busConnect); 154 | //console.log(" [*] Waiting for miner stats. To exit press CTRL+C."); 155 | }); 156 | }); 157 | } 158 | 159 | const qSensor = "sensor"; 160 | let sensor_channel = null; 161 | function sensorMessage(msg) { 162 | if (msg) { 163 | //msg.content.toString() 164 | //console.log(" [x] '%s'", "received sensor message"); 165 | sseConnection.send({ 166 | event: "full-cycle-sensor", 167 | data: msg.content.toString() 168 | }); 169 | } 170 | } 171 | 172 | function on_channel_open_sensor(err, ch) { 173 | if (err !== null) return bail(err, busConnect); 174 | sensor_channel = ch; 175 | ch.on("error", function (err) { 176 | console.error(err) 177 | //console.log("sensor channel Closed"); 178 | }); 179 | ch.assertQueue("", {exclusive: true}, function(err, ok) { 180 | var q = ok.queue; 181 | ch.bindQueue(q, qSensor, ""); 182 | ch.consume(q, sensorMessage, {noAck: true}, function(err, ok) { 183 | if (err !== null) return bail(err, busConnect); 184 | //console.log(" [*] Waiting for sensor. To exit press CTRL+C."); 185 | }); 186 | }); 187 | } 188 | 189 | if (busConnect) 190 | { 191 | busConnect.createChannel(on_channel_open_alert); 192 | busConnect.createChannel(on_channel_open_miner); 193 | busConnect.createChannel(on_channel_open_sensor); 194 | } 195 | 196 | sseConnection.on("close", function () { 197 | //console.log("lost sse connection"); 198 | if (alertChannel) alertChannel.close(); 199 | if (miner_channel) miner_channel.close(); 200 | if (sensor_channel) sensor_channel.close(); 201 | }); 202 | 203 | }); 204 | 205 | try { 206 | amqp.connect(services.messagebus.connection, on_connect); 207 | } 208 | catch(error) { 209 | console.error(error); 210 | } 211 | -------------------------------------------------------------------------------- /src/web/src/views/Pools/PoolsTable.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import 3 | Table, 4 | { TableBody, TableCell, TableHead, TableRow, TableFooter } from "material-ui/Table"; 5 | import Dialog, {DialogTitle, DialogContent, DialogActions} from "material-ui/Dialog"; 6 | import { FormControl } from "material-ui/Form"; 7 | import TextField from "material-ui/TextField"; 8 | import { 9 | Button 10 | // Table 11 | } from "components"; 12 | 13 | const tableColumnStyle = { 14 | paddingRight: "5px", 15 | paddingLeft: "5px", 16 | width: "100px", 17 | textOverflow: "ellipsis" 18 | } 19 | 20 | export default class PoolsTable extends Component { 21 | state = { 22 | pools: [], 23 | openPool: false, 24 | activeRowId : "", 25 | existingPoolName: "", 26 | poolName: "" 27 | }; 28 | 29 | handleOpenPool = (pool) => () => { 30 | this.setState({ activeRowId: pool.url+"|"+pool.user }); 31 | this.setState({ openPool: true }); 32 | this.setState({existingPoolName: pool.named_pool.name}); 33 | this.setState({poolName: pool.named_pool.name}); 34 | }; 35 | 36 | handleClosePool= () => { 37 | this.setState({ openPool: false }); 38 | }; 39 | 40 | handleNamePool = (ppool, newPoolName) => { 41 | const pool = ppool 42 | if (pool){ 43 | if (!pool.namedPool){ 44 | pool.namedPool = {name: newPoolName}; 45 | } 46 | pool.namedPool.name = newPoolName; 47 | } 48 | 49 | //this.handleSetPoolName(newPoolName); 50 | this.callApiSavePool(this.state.existingPoolName, pool) 51 | .then(res => this.setState({ })) 52 | .catch(err => console.log(err)); 53 | //todo: if there is an error should not close dialog 54 | this.handleClosePool(); 55 | }; 56 | 57 | callApiSavePool = async (existingPoolName, pool) => { 58 | let bod = { 59 | command: "save", 60 | parameter: "", 61 | id: "", 62 | entity: "pool", 63 | values: [ 64 | {name: pool.named_pool.name}, 65 | {pool_type: pool.pool_type}, 66 | {url: pool.url}, 67 | {user: pool.user}, 68 | {priority: pool.priority} 69 | ] 70 | } 71 | if (existingPoolName) { 72 | bod.id = {name: existingPoolName} 73 | } 74 | 75 | const response = await fetch("/api/save", { 76 | method: "POST", 77 | headers: { 78 | Accept: "application/json", 79 | "Content-Type": "application/json", 80 | }, 81 | body: JSON.stringify(bod) 82 | }); 83 | const body = await response.json(); 84 | if (response.status !== 200) throw Error(body.message); 85 | return body; 86 | }; 87 | 88 | renderPool(p) { 89 | const pool = p 90 | return ( 91 | 92 | 93 | {pool.pool_type} 94 | 95 | 96 | { 97 | pool.named_pool ? 98 | ( 99 | 102 | ) : 103 | ( 104 | 107 | ) 108 | } 109 | 110 | 111 | {pool.priority} 112 | 113 | 114 | {pool.url} 115 | 116 | 117 | {pool.user.slice(0,50)} 118 | 119 | 120 | {pool.password} 121 | 122 | 123 | ); 124 | } 125 | 126 | find(array, key) { 127 | return array.find((element) => { 128 | return element.url+"|"+element.user === key; 129 | }); 130 | } 131 | 132 | handleNameChange = event => { 133 | this.setState({ poolName: event.target.value }); 134 | }; 135 | 136 | handleSetPoolName= (value) => { 137 | this.setState({ poolName: value }); 138 | }; 139 | 140 | handleChange = name => event => { 141 | this.setState({ 142 | [name]: event.target.value, 143 | }); 144 | }; 145 | 146 | // 155 | 156 | tablehead = () => { 157 | return ( 158 | 159 | 160 | Type 161 | Name 162 | Priority 163 | Url 164 | User 165 | Password 166 | 167 | 168 | ); 169 | } 170 | 171 | // tableHeaderColor="primary" 172 | // tableHead={this.tablehead()} 173 | // tableData={[ 174 | // ["Dakota Rice", "Niger", "Oud-Turnhout", "$36,738"], 175 | // ["Minerva Hooper", "Curaçao", "Sinaai-Waas", "$23,789"], 176 | // ["Sage Rodriguez", "Netherlands", "Baileux", "$56,142"], 177 | // ["Philip Chaney", "Korea, South", "Overland Park", "$38,735"], 178 | // ["Doris Greene", "Malawi", "Feldkirchen in Kärnten", "$63,542"], 179 | // ["Mason Porter", "Chile", "Gloucester", "$78,615"] 180 | // ]} 181 | 182 | render() { 183 | const jpools = this.props.pools; 184 | const arrPools = jpools; 185 | const renderedPools = arrPools.map((p) => this.renderPool(p)); 186 | //console.log(arrPools.length.toString() + " pools"); 187 | const selectedPool = this.find(arrPools, this.state.activeRowId ); 188 | // if (selectedPool) 189 | // this.handleSetPoolName(selectedPool.named_pool.name); 190 | const tbhd = this.tablehead(); 191 | return ( 192 |
    193 | 195 | {tbhd} 196 | 197 | {renderedPools} 198 | 199 | 200 | 201 | 202 | 203 |
    204 | 205 | {selectedPool && this.state.openPool ? ( 206 | 210 | 211 | {selectedPool.named_pool ? "Edit" : "Add"} Pool Name 212 | 213 | 214 | 221 | 222 | 223 | 224 | 225 | 228 | 231 | 232 | 233 | ): null} 234 | 235 |
    236 | ); 237 | } 238 | } 239 | --------------------------------------------------------------------------------