From 36bdb1a6f01a2affda91c7000f8d58baff076da1 Mon Sep 17 00:00:00 2001 From: tc Date: Wed, 11 Jul 2018 12:41:31 +0200 Subject: [PATCH 1/7] user added --- src/users.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/users.js b/src/users.js index 34b1aec..c2dd438 100644 --- a/src/users.js +++ b/src/users.js @@ -182,6 +182,10 @@ const USERS = { 5501568626383: { mention: 'bob', email: 'w.bozek@selleo.com' + }, + 5501567016069: { + mention: 'tromik', + email: 't.romik@selleo.com' } } From 7d5a8c963b4c47b4d15e231b423628dfb07f6d81 Mon Sep 17 00:00:00 2001 From: tc Date: Wed, 11 Jul 2018 12:46:39 +0200 Subject: [PATCH 2/7] user added --- src/users.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/users.js b/src/users.js index c2dd438..aff94db 100644 --- a/src/users.js +++ b/src/users.js @@ -186,6 +186,10 @@ const USERS = { 5501567016069: { mention: 'tromik', email: 't.romik@selleo.com' + }, + 5497845585440: { + mention: 'Kacper', + email: 'k.wozniak@selleo.com' } } From e92249396037d96ce6f1108e3a61c8dad040f62d Mon Sep 17 00:00:00 2001 From: tc Date: Thu, 12 Jul 2018 12:09:07 +0200 Subject: [PATCH 3/7] add more users --- src/users.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/users.js b/src/users.js index aff94db..952cd48 100644 --- a/src/users.js +++ b/src/users.js @@ -190,6 +190,18 @@ const USERS = { 5497845585440: { mention: 'Kacper', email: 'k.wozniak@selleo.com' + }, + 5501425432406: { + mention: 'cs3b', + email: 'm.czyz@selleo.com' + }, + 5501567419089: { + mention: 'enka', + email: 'e.tabak@selleo.com' + }, + 5501524337388: { + mention: 'Arkadiusz Kwaśny', + email: 'a.kwasny@selleo.com' } } From e5c3a816d06e6b7564f2ca6ba7428449809beb17 Mon Sep 17 00:00:00 2001 From: tc Date: Thu, 12 Jul 2018 15:13:07 +0200 Subject: [PATCH 4/7] add user --- src/users.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/users.js b/src/users.js index 952cd48..3fe996b 100644 --- a/src/users.js +++ b/src/users.js @@ -202,6 +202,10 @@ const USERS = { 5501524337388: { mention: 'Arkadiusz Kwaśny', email: 'a.kwasny@selleo.com' + }, + 5501425353830: { + mention: 'chalec', + email: 'p.chalecki@selleo.com' } } From 078b81cdac83dc726749348ec7046428c5402e10 Mon Sep 17 00:00:00 2001 From: tc Date: Mon, 16 Jul 2018 14:39:47 +0200 Subject: [PATCH 5/7] add user --- src/users.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/users.js b/src/users.js index 3fe996b..6f07d23 100644 --- a/src/users.js +++ b/src/users.js @@ -206,6 +206,10 @@ const USERS = { 5501425353830: { mention: 'chalec', email: 'p.chalecki@selleo.com' + }, + 4402038656086: { + mention: 'grduch', + email: 'g.rduch@selleo.com' } } From 4c99ab59545df0fdf7870de652a81332de22a68d Mon Sep 17 00:00:00 2001 From: tc Date: Tue, 17 Jul 2018 15:44:56 +0200 Subject: [PATCH 6/7] change webhook --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 9e27bd0..3d7e3d5 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -SLACK_WEBHOOK=https://hooks.slack.com/services/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 +SLACK_WEBHOOK=https://hooks.slack.com/services/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 SLACK_LOG_WEBHOOK=https://hooks.slack.com/services/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 From 555f3fc5b7a3addb62993f2407f7bd426954f5c9 Mon Sep 17 00:00:00 2001 From: marekpolakowski Date: Tue, 17 Jul 2018 17:01:54 +0200 Subject: [PATCH 7/7] OAB-18 create admin panel --- .env | 2 - .env.example | 3 ++ .gitignore | 1 + client/components/Game.jsx | 14 +++-- .../components/admin-panel/ActionButton.jsx | 28 ++++++++++ client/components/admin-panel/AdminPanel.jsx | 53 +++++++++++++++++++ client/styles/admin_panel.scss | 43 +++++++++++++++ client/styles/game.scss | 1 + client/wrappers/withSocket.jsx | 26 +++++++++ package.json | 1 + src/server.js | 23 ++++---- .../handleAdminPanelBroadcasts.js | 32 +++++++++++ src/serverModules/handleSpinResult.js | 16 ++++++ src/users.js | 1 + src/utils/raspberryOnly.js | 7 +++ webpack.config.js | 8 ++- yarn.lock | 18 +++++++ 17 files changed, 261 insertions(+), 16 deletions(-) delete mode 100644 .env create mode 100644 .env.example create mode 100644 client/components/admin-panel/ActionButton.jsx create mode 100644 client/components/admin-panel/AdminPanel.jsx create mode 100644 client/styles/admin_panel.scss create mode 100644 client/wrappers/withSocket.jsx create mode 100644 src/serverModules/handleAdminPanelBroadcasts.js create mode 100644 src/serverModules/handleSpinResult.js create mode 100644 src/utils/raspberryOnly.js diff --git a/.env b/.env deleted file mode 100644 index 3d7e3d5..0000000 --- a/.env +++ /dev/null @@ -1,2 +0,0 @@ -SLACK_WEBHOOK=https://hooks.slack.com/services/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 -SLACK_LOG_WEBHOOK=https://hooks.slack.com/services/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 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e3ad662 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +SLACK_WEBHOOK=https://hooks.slack.com/123/456/789 +SLACK_LOG_WEBHOOK=https://hooks.slack.com/123/456/789 +RASPBERRY=0|1 diff --git a/.gitignore b/.gitignore index c7c28bb..1cc25d8 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ images/* .DS_Store +.env diff --git a/client/components/Game.jsx b/client/components/Game.jsx index 49528a5..7c4f5e7 100644 --- a/client/components/Game.jsx +++ b/client/components/Game.jsx @@ -4,14 +4,16 @@ import socketIOClient from 'socket.io-client' import { logResult, resultResponse } from '../modules/result' import { ToastContainer, toast } from 'react-toastify' +import withSocket from '@wrappers/withSocket' +import AdminPanel from '@components/admin-panel/AdminPanel' + const ROLLERS = ['left', 'center', 'right'] -export default class Game extends React.Component { +class Game extends React.Component { constructor(props) { super(props) this.state = {} - this.socket = socketIOClient('http://localhost:3000') ROLLERS.forEach(roller => { this[roller] = React.createRef() @@ -19,8 +21,9 @@ export default class Game extends React.Component { } componentDidMount() { - this.socket.on('SPIN_REQUEST', forcedSpinTo => this._spinMachine(forcedSpinTo)) - this.socket.on('NOTIFY', (type, message) => toast[type](message)) + const { socket } = this.props; + socket.on('SPIN_REQUEST', forcedSpinTo => this._spinMachine(forcedSpinTo)) + socket.on('NOTIFY', (type, message) => toast[type](message)) } _spinMachine(forcedSpinTo) { @@ -51,9 +54,12 @@ export default class Game extends React.Component { return (
+
{this._createRollers()}
) } } + +export default withSocket(Game); diff --git a/client/components/admin-panel/ActionButton.jsx b/client/components/admin-panel/ActionButton.jsx new file mode 100644 index 0000000..1ed5a19 --- /dev/null +++ b/client/components/admin-panel/ActionButton.jsx @@ -0,0 +1,28 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +import withSocket from '@wrappers/withSocket' + +class ActionButton extends Component { + _onClick(message) { + this.props.socket.emit(message) + } + + render() { + const { text, socketAction } = this.props + + return ( + this._onClick(socketAction)}>{text} + ) + } +} + +ActionButton.propTypes = { + text: PropTypes.string.isRequired, + socketAction: PropTypes.string.isRequired, + socket: PropTypes.shape({ + emit: PropTypes.func.isRequired + }).isRequired +} + +export default withSocket(ActionButton) diff --git a/client/components/admin-panel/AdminPanel.jsx b/client/components/admin-panel/AdminPanel.jsx new file mode 100644 index 0000000..ad4076a --- /dev/null +++ b/client/components/admin-panel/AdminPanel.jsx @@ -0,0 +1,53 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +import ActionButton from '@components/admin-panel/ActionButton' +import withSocket from '@wrappers/withSocket' +import '@styles/admin_panel' + +class AdminPanel extends Component { + constructor(props) { + super(props) + this.state = { active: false } + this._setActive = this._setActive.bind(this) + } + + componentDidMount() { + this.props.socket.on('SET_ADMIN_PANEL_ACTIVE', this._setActive) + } + + _setActive(value) { + this.setState({ active: value }) + } + + _getWrapperClass(active) { + const classes = ['admin-panel'] + if (active) { classes.push('active') } + return classes.join(' ') + } + + render() { + const { active } = this.state + + return ( + + ) + } +} + +AdminPanel.propTypes = { + socket: PropTypes.shape({ + on: PropTypes.func.isRequired + }).isRequired +} + +export default withSocket(AdminPanel) diff --git a/client/styles/admin_panel.scss b/client/styles/admin_panel.scss new file mode 100644 index 0000000..4392f10 --- /dev/null +++ b/client/styles/admin_panel.scss @@ -0,0 +1,43 @@ +.admin-panel { + position: absolute; + width: 100%; + height: 100%; + z-index: 10; + padding: 15px; + box-sizing: border-box; + will-change: top; + transition: top 0.6s cubic-bezier(0.4, 0.2, 0, 1); + top: -100%; + background: #30373f; + + &.active { + top: 0%; + } + + &__actions { + margin: 0 -10px 20px; + display: flex; + + a { + font-family: Helvetica, sans-serif; + margin: 0 10px 20px; + padding: 0 20px; + background: #cccccc; + border: 1px solid #b1b2b3; + color: #6d6972; + border-radius: 3px; + font-size: 0.8em; + text-transform: uppercase; + line-height: 2.25em; + font-weight: bold; + } + + a, a:hover, a:active, a:focus, a:visited { + text-decoration: none; + } + + a.close { + margin-left: auto; + } + } +} diff --git a/client/styles/game.scss b/client/styles/game.scss index 5c2a789..4eac8e3 100644 --- a/client/styles/game.scss +++ b/client/styles/game.scss @@ -14,6 +14,7 @@ body { display: flex; align-items: center; justify-content: center; + position: relative; } .roller-wrapper { diff --git a/client/wrappers/withSocket.jsx b/client/wrappers/withSocket.jsx new file mode 100644 index 0000000..aa4d21d --- /dev/null +++ b/client/wrappers/withSocket.jsx @@ -0,0 +1,26 @@ +import React, { Component } from 'react' +import socketIOClient from 'socket.io-client' + +let socket + +export default function withSocket(WrappedComponent) { + class WithSocket extends Component { + constructor(props) { + super(props) + + if (!socket) { + socket = socketIOClient('http://localhost:3000') + } + } + + render() { + return ( + + ) + } + } + + WithSocket.displayName = `WithSocket(${WrappedComponent.name})` + + return WithSocket; +} diff --git a/package.json b/package.json index cc7c77e..ae68750 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "react-toastify": "^4.1.0", "redis": "^2.8.0", "serialport": "^6.2.1", + "shelljs": "^0.8.2", "slack-webhook": "^1.0.0", "socket.io": "^2.1.1", "socket.io-client": "^2.1.1" diff --git a/src/server.js b/src/server.js index ecd6486..2109def 100644 --- a/src/server.js +++ b/src/server.js @@ -6,7 +6,6 @@ import http from 'http' import reader, { parseData } from './rfid' import redis from 'redis' import moment from 'moment' -import servo from './servo' import sendPhoto from './queue' import Slack from './slack' import socketIO from 'socket.io' @@ -27,12 +26,13 @@ let readyForSpin = false let socketClient = null let user = null +import handleAdminPanelBroadcasts from './serverModules/handleAdminPanelBroadcasts' +import handleSpinResult from './serverModules/handleSpinResult' + app.use(webpackMiddleware(webpack(webpackConfig))) server.listen(3000) function _rollRequest(userId) { - user = getUser(userId) - if (user === undefined) { slack.log(userId) socketClient.emit('NOTIFY', 'error', 'please go to @czana') @@ -82,15 +82,20 @@ io.on('connection', client => { client.on('SPIN_ENDED', result => { readyForSpin = true - - if (result.win) { - if (result.cashPrize) servo.move() - slack.post(user.mention, result.icon, result.cashPrize ? '$$$' : '2 Kudos!') - } + handleSpinResult(result, user); }) + + handleAdminPanelBroadcasts(client); }) reader.on('data', data => { const userId = parseData(data) - _rollRequest(userId) + user = getUser(userId) + + if (user && user.admin) { + socketClient.emit('SET_ADMIN_PANEL_ACTIVE', true); + } else { + socketClient.emit('SET_ADMIN_PANEL_ACTIVE', false); + _rollRequest(userId) + } }) diff --git a/src/serverModules/handleAdminPanelBroadcasts.js b/src/serverModules/handleAdminPanelBroadcasts.js new file mode 100644 index 0000000..ea381a3 --- /dev/null +++ b/src/serverModules/handleAdminPanelBroadcasts.js @@ -0,0 +1,32 @@ +import shell from 'shelljs' + +import raspberryOnly from '../utils/raspberryOnly' + +let servo + +raspberryOnly(() => { + servo = require('../servo')['default'] +}) + +export default (client) => { + client.on('@admin/REBOOT', () => { + raspberryOnly(() => { + shell.exec('sudo reboot') + client.emit('SET_ADMIN_PANEL_ACTIVE', false); + }) + }) + + client.on('@admin/SHUTDOWN', () => { + raspberryOnly(() => { + shell.exec('sudo shutdown -P now') + client.emit('SET_ADMIN_PANEL_ACTIVE', false); + }) + }) + + client.on('@admin/TRIGGER_SERVO', () => { + raspberryOnly(() => { + servo.move() + client.emit('SET_ADMIN_PANEL_ACTIVE', false); + }) + }) +} diff --git a/src/serverModules/handleSpinResult.js b/src/serverModules/handleSpinResult.js new file mode 100644 index 0000000..17256dc --- /dev/null +++ b/src/serverModules/handleSpinResult.js @@ -0,0 +1,16 @@ +import raspberryOnly from '../utils/raspberryOnly' + +let servo + +raspberryOnly(() => { + servo = require('../servo') +}) + +export default (result, user) => { + if (result.win) { + raspberryOnly(() => { + if (result.cashPrize) { servo.move() } + }) + slack.post(user.mention, result.icon, result.cashPrize ? '$$$' : '2 Kudos!') + } +} diff --git a/src/users.js b/src/users.js index 6f07d23..3a69981 100644 --- a/src/users.js +++ b/src/users.js @@ -2,6 +2,7 @@ const USERS = { 5501512322897: { mention: 'czana', email: 't.czana@selleo.com', + admin: true }, 4402038631408: { mention: 'bart', diff --git a/src/utils/raspberryOnly.js b/src/utils/raspberryOnly.js new file mode 100644 index 0000000..c635721 --- /dev/null +++ b/src/utils/raspberryOnly.js @@ -0,0 +1,7 @@ +require('dotenv').config() + +export default (theFunction) => { + if (process.env.RASPBERRY === '1') { + theFunction() + } +} diff --git a/webpack.config.js b/webpack.config.js index 3e367f8..a63d300 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,10 +1,16 @@ import HtmlWebpackPlugin from 'html-webpack-plugin' +import path from 'path' export default { mode: 'development', entry: './client/index.jsx', resolve: { - extensions: ['.js', '.jsx', '.scss'] + extensions: ['.js', '.jsx', '.scss'], + alias: { + '@components': path.resolve(__dirname, 'client/components'), + '@styles': path.resolve(__dirname, 'client/styles'), + '@wrappers': path.resolve(__dirname, 'client/wrappers') + } }, output: { path: '/', diff --git a/yarn.lock b/yarn.lock index be134c8..0f6c26e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3344,6 +3344,10 @@ ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" +interpret@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" + invariant@^2.2.2: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -5517,6 +5521,12 @@ readline-utils@^2.2.1, readline-utils@^2.2.3: strip-color "^0.1.0" window-size "^1.1.0" +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + dependencies: + resolve "^1.1.6" + redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -5980,6 +5990,14 @@ shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" +shelljs@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.2.tgz#345b7df7763f4c2340d584abb532c5f752ca9e35" + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"