Initial Commit

This commit is contained in:
Patrick McDonagh
2018-06-04 19:42:46 -05:00
parent 7c7d9f0f74
commit 9df0b5775d
24 changed files with 23124 additions and 0 deletions

3
.eslintrc Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "airbnb"
}

67
.gitignore vendored Normal file
View File

@@ -0,0 +1,67 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless

4
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"eslint.enable": true,
"editor.tabSize": 2
}

4050
app/build/bundle.js Normal file

File diff suppressed because one or more lines are too long

20
app/index.html Executable file
View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>POCloud Admin</title>
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="root"></div>
</body>
<!-- Compiled and minified JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
<script src="./build/bundle.js"></script>
</html>

2
app/src/Meshify.js Normal file
View File

@@ -0,0 +1,2 @@
const baseURL = 'https://henrypump.meshify.com/api/v3';
export default baseURL;

View File

@@ -0,0 +1,69 @@
import { encode } from 'base-64';
import axios from 'axios';
import { persistor } from '../renderer_process';
import {
EMAIL_CHANGED,
PASSWORD_CHANGED,
LOGIN_USER_ATTEMPT,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAIL,
LOGOUT,
COMPANY_DATA_RECEIVED,
} from './types';
import baseURL from '../Meshify';
export const emailChanged = (text) => {
return {
type: EMAIL_CHANGED,
payload: text,
};
};
export const passwordChanged = (text) => {
return {
type: PASSWORD_CHANGED,
payload: text,
};
};
const loginUserSuccess = (dispatch, user, authToken) => {
dispatch({
type: LOGIN_USER_SUCCESS,
payload: { user, authToken },
});
};
const loginUserFail = (dispatch) => {
dispatch({ type: LOGIN_USER_FAIL });
};
export const loginUser = ({ email, password }) => {
const tokenPart = encode(`${email}:${password}`);
const authToken = `Basic ${tokenPart}`;
return (dispatch) => {
dispatch({ type: LOGIN_USER_ATTEMPT });
axios.get(`${baseURL}/users/me`, { headers: { Authorization: authToken } })
.then((response) => {
loginUserSuccess(dispatch, response.data, authToken);
})
.catch(() => loginUserFail(dispatch));
axios.get(`${baseURL}/companies`, { headers: { Authorization: authToken } })
.then((response) => {
dispatch({ type: COMPANY_DATA_RECEIVED, payload: response.data });
})
.catch((err) => {
console.log('AUTH', authToken);
console.log(err);
});
};
};
export const logoutUser = () => {
persistor.purge();
return ({
type: LOGOUT,
});
};

2
app/src/actions/index.js Normal file
View File

@@ -0,0 +1,2 @@
export * from './authActions';
export * from './meshifyActions';

View File

@@ -0,0 +1,34 @@
import axios from 'axios';
import baseURL from '../Meshify';
import {
COMPANY_DATA_RECEIVED,
GATEWAY_DATA_RECEIVED,
DEVICETYPE_DATA_RECEIVED,
DEVICE_DATA_RECEIVED,
} from './types';
export const getData = ({ authToken }) => {
if (!authToken) {
return { type: 'dummy' };
}
return (dispatch) => {
axios.get(`${baseURL}/companies`, { headers: { Authorization: authToken } })
.then((response) => {
dispatch({ type: COMPANY_DATA_RECEIVED, payload: response.data });
axios.get(`${baseURL}/devicetypes`, { headers: { Authorization: authToken } })
.then((response) => {
dispatch({ type: DEVICETYPE_DATA_RECEIVED, payload: response.data });
axios.get(`${baseURL}/gateways`, { headers: { Authorization: authToken } })
.then((gtwResponse) => {
dispatch({ type: GATEWAY_DATA_RECEIVED, payload: gtwResponse.data });
axios.get(`${baseURL}/devices`, { headers: { Authorization: authToken } })
.then((deviceResponse) => {
dispatch({ type: DEVICE_DATA_RECEIVED, payload: deviceResponse.data });
});
});
});
});
};
};

12
app/src/actions/types.js Normal file
View File

@@ -0,0 +1,12 @@
export const LOGIN_USER_ATTEMPT = 'login_user_attempt';
export const LOGIN_USER_SUCCESS = 'login_user_success';
export const LOGIN_USER_FAIL = 'login_user_fail';
export const LOGOUT = 'logout';
export const EMAIL_CHANGED = 'email_changed';
export const PASSWORD_CHANGED = 'password_changed';
export const COMPANY_DATA_RECEIVED = 'company_data_received';
export const GATEWAY_DATA_RECEIVED = 'gateway_data_received';
export const DEVICETYPE_DATA_RECEIVED = 'devicetype_data_received';
export const DEVICE_DATA_RECEIVED = 'device_data_received';

BIN
app/src/assets/iconTemplate.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

60
app/src/components/Header.jsx Executable file
View File

@@ -0,0 +1,60 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { logoutUser } from '../actions';
class Header extends Component {
onLogoutButtonClicked(event) {
event.preventDefault();
this.props.logoutUser();
this.props.history.push('/login');
}
render() {
const { authToken } = this.props.auth;
if (authToken) {
return (
<nav>
<div className="nav-wrapper">
<ul className="right">
<li>
<button
className="btn waves-effect waves-light"
onClick={this.onLogoutButtonClicked.bind(this)}
style={{ marginRight: 15 }}
>
Log Out
</button>
</li>
</ul>
<ul>
<li>
<Link to="/main">Main</Link>
</li>
</ul>
</div>
</nav>
);
}
return (
<nav>
<div className="nav-wrapper">
<ul>
<li>
<Link to="/login">Login</Link>
</li>
</ul>
</div>
</nav>
);
}
}
const mapStateToProps = state => ({
auth: state.auth,
});
export default connect(mapStateToProps, { logoutUser })(Header);

View File

@@ -0,0 +1,71 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { emailChanged, passwordChanged, loginUser } from '../actions';
class Login extends Component {
onEmailChange(event) {
this.props.emailChanged(event.target.value);
}
onPasswordChange(event) {
this.props.passwordChanged(event.target.value);
}
onButtonPress(event) {
event.preventDefault();
const { email, password } = this.props.auth;
this.props.loginUser({ email, password });
}
render() {
const { email, password, authToken } = this.props.auth;
if (authToken) {
this.props.history.push('/main');
}
return (
<div className="row">
<h1>Login</h1>
<form className="col s12">
<div className="input-field col s12">
<input
placeholder="email@email.com"
id="email"
type="email"
value={email}
onChange={this.onEmailChange.bind(this)}
/>
<label htmlFor="email">Email Address</label>
</div>
<div className="input-field col s12">
<input
id="password"
type="password"
value={password}
onChange={this.onPasswordChange.bind(this)}
/>
<label htmlFor="password">Password</label>
</div>
<button
className="btn waves-effect waves-light"
type="submit"
name="action"
onClick={this.onButtonPress.bind(this)}
>
Submit <i className="material-icons right">send</i>
</button>
</form>
</div>
);
}
}
const mapStateToProps = state => ({
auth: state.auth,
});
export default connect(mapStateToProps, { emailChanged, passwordChanged, loginUser })(Login);

View File

@@ -0,0 +1,31 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getData } from '../actions';
class Main extends Component {
componentWillMount() {
this.props.getData();
}
render() {
return (
<h1>Meshify Devices</h1>
<table>
<thead>
</thead>
</table>
);
}
}
const mapStateToProps = (state) => {
return {
auth: state.auth,
};
};
export default connect(mapStateToProps, { getData })(Main);

View File

@@ -0,0 +1,69 @@
import _ from 'lodash';
import {
EMAIL_CHANGED,
PASSWORD_CHANGED,
LOGIN_USER_ATTEMPT,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAIL,
LOGOUT,
COMPANY_DATA_RECEIVED,
} from '../actions/types';
const INITIAL_STATE = {
email: '',
password: '',
authToken: '',
error: '',
user: {
first: '',
last: '',
companyName: '',
},
loading: false,
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case EMAIL_CHANGED:
return { ...state, email: action.payload };
case PASSWORD_CHANGED:
return { ...state, password: action.payload };
case LOGIN_USER_ATTEMPT:
return { ...state, loading: true, error: '' };
case LOGIN_USER_SUCCESS:
return {
...state,
password: '',
authToken: action.payload.authToken,
user: action.payload.user,
loading: false,
};
case LOGIN_USER_FAIL:
return {
...state,
error: 'Authentication failed!',
password: '',
loading: false,
};
case LOGOUT:
return INITIAL_STATE;
case COMPANY_DATA_RECEIVED:
return {
...state,
user: {
...state.user,
companyName: _.keyBy(action.payload, c => c.id)[state.user.companyId].name,
},
};
default:
return state;
}
};

View File

@@ -0,0 +1,6 @@
import { combineReducers } from 'redux';
import AuthReducer from './authReducer';
export default combineReducers({
auth: AuthReducer,
});

40
app/src/renderer_process.jsx Executable file
View File

@@ -0,0 +1,40 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { PersistGate } from 'redux-persist/integration/react';
import ReduxThunk from 'redux-thunk';
import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';
import Header from './components/Header';
import Login from './components/Login';
import Main from './components/Main';
import reducers from './reducers';
const persistConfig = { key: 'root', storage };
const persistedReducer = persistReducer(persistConfig, reducers);
const store = createStore(persistedReducer, {}, applyMiddleware(ReduxThunk));
export const persistor = persistStore(store);
const App = () => (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<BrowserRouter>
<div>
<Header />
<Switch>
<Route path="/main" component={Main} />
<Route path="/login" component={Login} default />
<Redirect to="/login" />
</Switch>
</div>
</BrowserRouter>
</PersistGate>
</Provider>
);
ReactDOM.render(<App />, document.getElementById('root'));

13
main_process.js Normal file
View File

@@ -0,0 +1,13 @@
const electron = require('electron');
const { app, BrowserWindow } = electron;
let mainWindow;
app.on('ready', () => {
mainWindow = new BrowserWindow({
height: 600,
width: 800,
});
mainWindow.loadURL(`file://${__dirname}/app/index.html`);
});

13578
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

59
package.json Executable file
View File

@@ -0,0 +1,59 @@
{
"name": "pocloud-admin",
"version": "1.0.0",
"description": "POCloud Admin App",
"main": "main_process.js",
"scripts": {
"bundle": "webpack --mode development",
"serve": "electron .",
"start": "npm-run-all --parallel bundle serve",
"pack": "electron-builder --dir",
"dist": "electron-builder -mwl"
},
"author": "Patric McDonagh",
"devDependencies": {
"eslint-config-airbnb": "^16.1.0",
"npm-run-all": "^4.1.3",
"babel-core": "^6.26.0",
"babel-eslint": "^8.2.2",
"babel-loader": "^7.1.4",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"electron-builder": "^20.8.1",
"electron-reload": "^1.2.2",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "^1.1.10",
"jest": "^22.4.3",
"mini-css-extract-plugin": "^0.4.0",
"style-loader": "^0.20.3",
"webpack": "^4.1.1",
"webpack-cli": "^2.0.11"
},
"dependencies": {
"axios": "^0.18.0",
"base-64": "^0.1.0",
"electron": "^2.0.2",
"electron-localshortcut": "^1.1.1",
"lodash": "^4.17.10",
"moment": "^2.18.1",
"moment-duration-format": "^1.3.0",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-redux": "^5.0.7",
"react-router": "^4.0.0",
"react-router-dom": "^4.0.0",
"redux": "^4.0.0",
"redux-persist": "^5.9.1",
"redux-thunk": "^2.3.0"
},
"build": {
"appId": "com.patrickjmcd.pocloud-admin",
"mac": {
"target": "default"
},
"win": {
"target": "portable"
}
}
}

51
webpack.config.js Executable file
View File

@@ -0,0 +1,51 @@
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
watch: false,
target: 'electron-main',
entry: ['./app/src/renderer_process.jsx'],
output: {
path: `${__dirname}/app/build`,
publicPath: 'build/',
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
options: {
presets: ['es2015', 'react', 'stage-0'],
},
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
],
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
query: {
name: '[name].[ext]?[hash]',
},
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'main.css',
chunkFilename: '[id].css',
}),
],
resolve: {
extensions: ['.js', '.json', '.jsx'],
},
};

4883
yarn.lock Normal file

File diff suppressed because it is too large Load Diff