├── 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 |
26 | {children}
27 |
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 |
39 | {makeBrand()}
40 |
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 |
29 | You need to enable JavaScript to run this app.
30 |
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 |
23 |
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 | this.discover()}
54 | >
55 | Discover
56 |
57 |
58 |
59 | this.provision()}
63 | >
64 | Provision
65 |
66 |
67 |
68 | this.monitor()}
72 | >
73 | Monitor
74 |
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 | {this.handleCloseDialog()}} color="primary" autoFocus>
80 | Close
81 |
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 |
57 |
58 |
59 | {this.handleCloseCamera()}} color="primary" autoFocus>
60 | Close
61 |
62 |
63 |
64 | ): null}
65 |
66 | );
67 | } else {
68 | if (this.props.mode === "small") {
69 | return (
70 |
74 | );
75 | } else {
76 | return (
77 |
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 |
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 | [](https://scrutinizer-ci.com/g/dfoderick/fullcyclereact/build-status/master)
4 | [](https://app.codacy.com/app/dfoderick/fullcyclereact?utm_source=github.com&utm_medium=referral&utm_content=dfoderick/fullcyclereact&utm_campaign=Badge_Grade_Dashboard)
5 | [](https://scrutinizer-ci.com/g/dfoderick/fullcyclereact/?branch=master)
6 | [](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 | 
11 | 
12 | 
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={Update Profile }
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 |
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 |
100 | { pool.named_pool.name }
101 |
102 | ) :
103 | (
104 |
105 | Add Name
106 |
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 | {this.handleClosePool()}} color="primary">
226 | Cancel
227 |
228 | {this.handleNamePool(selectedPool , this.state.poolName)}} color="primary" autoFocus>
229 | Save
230 |
231 |
232 |
233 | ): null}
234 |
235 |
236 | );
237 | }
238 | }
239 |
--------------------------------------------------------------------------------