Integrate login and auth server API
This commit is contained in:
		@@ -25,9 +25,6 @@ SECRET_KEY = 'h$p_vf^h$h@cebvgeynhda3-@i&+f-(3k9w-qsftn_+!-o_)$0'
 | 
				
			|||||||
# SECURITY WARNING: don't run with debug turned on in production!
 | 
					# SECURITY WARNING: don't run with debug turned on in production!
 | 
				
			||||||
DEBUG = True
 | 
					DEBUG = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ALLOWED_HOSTS = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Application definition
 | 
					# Application definition
 | 
				
			||||||
 | 
					
 | 
				
			||||||
INSTALLED_APPS = [
 | 
					INSTALLED_APPS = [
 | 
				
			||||||
@@ -39,6 +36,7 @@ INSTALLED_APPS = [
 | 
				
			|||||||
    'django.contrib.staticfiles',
 | 
					    'django.contrib.staticfiles',
 | 
				
			||||||
    'rest_framework',
 | 
					    'rest_framework',
 | 
				
			||||||
    'rest_framework.authtoken',
 | 
					    'rest_framework.authtoken',
 | 
				
			||||||
 | 
					    'corsheaders',
 | 
				
			||||||
    'authserver.api',
 | 
					    'authserver.api',
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -49,8 +47,14 @@ REST_FRAMEWORK = {
 | 
				
			|||||||
    ),
 | 
					    ),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#CORS_ORIGIN_WHITELIST = (
 | 
				
			||||||
 | 
					#    'localhost:8000',
 | 
				
			||||||
 | 
					#)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CORS_ORIGIN_ALLOW_ALL = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MIDDLEWARE = [
 | 
					MIDDLEWARE = [
 | 
				
			||||||
 | 
					    'corsheaders.middleware.CorsMiddleware',
 | 
				
			||||||
    'django.middleware.security.SecurityMiddleware',
 | 
					    'django.middleware.security.SecurityMiddleware',
 | 
				
			||||||
    'django.contrib.sessions.middleware.SessionMiddleware',
 | 
					    'django.contrib.sessions.middleware.SessionMiddleware',
 | 
				
			||||||
    'django.middleware.common.CommonMiddleware',
 | 
					    'django.middleware.common.CommonMiddleware',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
certifi==2018.8.24
 | 
					certifi==2018.8.24
 | 
				
			||||||
chardet==3.0.4
 | 
					chardet==3.0.4
 | 
				
			||||||
Django==2.1.1
 | 
					Django==2.1.1
 | 
				
			||||||
 | 
					django-cors-headers==2.4.0
 | 
				
			||||||
djangorestframework==3.8.2
 | 
					djangorestframework==3.8.2
 | 
				
			||||||
idna==2.7
 | 
					idna==2.7
 | 
				
			||||||
Pillow==5.2.0
 | 
					Pillow==5.2.0
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,46 +2,71 @@ import React, { Component } from 'react';
 | 
				
			|||||||
import Categories from './Categories';
 | 
					import Categories from './Categories';
 | 
				
			||||||
import Category from './Category';
 | 
					import Category from './Category';
 | 
				
			||||||
import Tool from './Tool';
 | 
					import Tool from './Tool';
 | 
				
			||||||
import { Container, Dimmer, Dropdown, Header, Icon, Item, Loader, Menu, Segment, Input } from 'semantic-ui-react';
 | 
					import { Button, Confirm, Container, Dimmer, Form, Header, Icon, Item, Label, Loader, Menu, Message, Segment, Input } from 'semantic-ui-react';
 | 
				
			||||||
import { Link, Route } from 'react-router-dom';
 | 
					import { Link, Route } from 'react-router-dom';
 | 
				
			||||||
import io from 'socket.io-client';
 | 
					import io from 'socket.io-client';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Move to env var
 | 
					// Move to env var
 | 
				
			||||||
const SERVER_URL = 'http://localhost:8080';
 | 
					const SOCKET_SERVER_URL = 'http://localhost:8080';
 | 
				
			||||||
 | 
					const AUTH_SERVER_URL = 'http://localhost:8000';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class App extends Component {
 | 
					class App extends Component {
 | 
				
			||||||
	constructor() {
 | 
						constructor() {
 | 
				
			||||||
		super();
 | 
							super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.socket = io(SERVER_URL);
 | 
							this.socket = io(SOCKET_SERVER_URL);
 | 
				
			||||||
 | 
							this.storage = typeof localStorage !== 'undefined';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let token = this.storage ? localStorage.getItem('token') : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.state = {
 | 
							this.state = {
 | 
				
			||||||
 | 
								login: {token: token, error: null, username: '', password: '', confirmLogout: false},
 | 
				
			||||||
			user: null,
 | 
								user: null,
 | 
				
			||||||
			toolData: null,
 | 
								toolData: null,
 | 
				
			||||||
			toolStatus: null,
 | 
								toolStatus: null,
 | 
				
			||||||
			connected: false,
 | 
								connected: false,
 | 
				
			||||||
 | 
								network: true,
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						getUser = () => {
 | 
				
			||||||
 | 
							fetch(AUTH_SERVER_URL + '/user/', {
 | 
				
			||||||
 | 
								headers: {'Authorization': 'Token ' + this.state.login.token},
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
								.then(response => {
 | 
				
			||||||
 | 
									if (response.ok) {
 | 
				
			||||||
 | 
										response.json().then(data => this.setState({ user: data[0] }));
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										this.handleLogout();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								.catch(error => {
 | 
				
			||||||
 | 
									console.log(error)
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	componentDidMount() {
 | 
						componentDidMount() {
 | 
				
			||||||
		fetch(SERVER_URL + '/api/tooldata')
 | 
							fetch(AUTH_SERVER_URL + '/tooldata/')
 | 
				
			||||||
			.then(response => response.json())
 | 
								.then(response => response.json())
 | 
				
			||||||
			.then(data => this.setState({ toolData: data }));
 | 
								.then(data => this.setState({ toolData: data }))
 | 
				
			||||||
		fetch(SERVER_URL + '/api/user')
 | 
								.catch(error => {
 | 
				
			||||||
			.then(response => response.json())
 | 
									console.log(error)
 | 
				
			||||||
			.then(data => this.setState({ user: data }));
 | 
									this.setState({network: false});
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (this.state.login.token) this.getUser();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.socket.on('toolStatus', toolStatus =>
 | 
							this.socket.on('toolStatus', toolStatus =>
 | 
				
			||||||
			this.setState({ toolStatus: toolStatus })
 | 
								this.setState({ toolStatus: toolStatus })
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.socket.on('connect', toolStatus =>
 | 
							this.socket.on('connect', () =>
 | 
				
			||||||
			this.setState({ connected: true })
 | 
								this.setState({ connected: true })
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.socket.on('disconnect', toolStatus =>
 | 
							this.socket.on('disconnect', () => {
 | 
				
			||||||
			this.setState({ toolStatus: null, connected: false })
 | 
								this.setState({ toolStatus: null, connected: false });
 | 
				
			||||||
		);
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	componentWillUnmount() {
 | 
						componentWillUnmount() {
 | 
				
			||||||
@@ -50,63 +75,161 @@ class App extends Component {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	requestInterlock = change => {
 | 
						requestInterlock = change => {
 | 
				
			||||||
		this.socket.emit('requestInterlock', {
 | 
							this.socket.emit('requestInterlock', {
 | 
				
			||||||
			username: this.state.user.username,
 | 
								token: this.state.login.token,
 | 
				
			||||||
			change: change,
 | 
								change: change,
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						handleChange = event => {
 | 
				
			||||||
 | 
							const name = event.target.name;
 | 
				
			||||||
 | 
							const value = event.target.value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.setState({
 | 
				
			||||||
 | 
								login: {...this.state.login, [name]: value}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						handleSubmit = () => {
 | 
				
			||||||
 | 
							const login = this.state.login;
 | 
				
			||||||
 | 
							fetch(AUTH_SERVER_URL + '/login/', {
 | 
				
			||||||
 | 
								method: 'POST',
 | 
				
			||||||
 | 
								headers: {'Content-Type': 'application/json; charset=utf-8'},
 | 
				
			||||||
 | 
								body: JSON.stringify({'username': login.username, 'password': login.password})
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
								.then(response => {
 | 
				
			||||||
 | 
									if (response.ok) {
 | 
				
			||||||
 | 
										response.json().then(data => {
 | 
				
			||||||
 | 
											this.setState({login: {...login, ...data, error: false}});
 | 
				
			||||||
 | 
											if (this.storage) localStorage.setItem('token', data.token);
 | 
				
			||||||
 | 
											this.getUser();
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										this.setState({login: {...login, error: true}});
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								.catch(error => {
 | 
				
			||||||
 | 
									console.log(error)
 | 
				
			||||||
 | 
									this.setState({network: false});
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						confirmLogout = () => {
 | 
				
			||||||
 | 
							if (this.state.login.token) {
 | 
				
			||||||
 | 
								this.setState({
 | 
				
			||||||
 | 
									login: {...this.state.login, confirmLogout: true}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cancelLogout = () => {
 | 
				
			||||||
 | 
							this.setState({
 | 
				
			||||||
 | 
								login: {...this.state.login, confirmLogout: false}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						handleLogout = () => {
 | 
				
			||||||
 | 
							this.setState({
 | 
				
			||||||
 | 
								login: {...this.state.login, token: null, confirmLogout: false}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							localStorage.removeItem('token');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						handleRefresh = () => {
 | 
				
			||||||
 | 
							window.location.reload();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	render() {
 | 
						render() {
 | 
				
			||||||
 | 
							const login = this.state.login;
 | 
				
			||||||
 | 
							const user = this.state.user;
 | 
				
			||||||
		const toolData = this.state.toolData;
 | 
							const toolData = this.state.toolData;
 | 
				
			||||||
		const toolStatus = this.state.toolStatus;
 | 
							const toolStatus = this.state.toolStatus;
 | 
				
			||||||
		const user = this.state.user;
 | 
					 | 
				
			||||||
		const connected = this.state.connected;
 | 
							const connected = this.state.connected;
 | 
				
			||||||
 | 
							const network = this.state.network;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return (
 | 
							return (
 | 
				
			||||||
			<div>
 | 
								network ?
 | 
				
			||||||
				<Menu fixed='top' borderless inverted>
 | 
									<div>
 | 
				
			||||||
					<Menu.Item>
 | 
										<Menu fixed='top' borderless inverted>
 | 
				
			||||||
						<Icon name='bars' size='big' />
 | 
											<Menu.Item onClick={this.confirmLogout}>
 | 
				
			||||||
					</Menu.Item>
 | 
												<Icon name='user close' size='big' />
 | 
				
			||||||
					<Menu.Item as={Link} to='/'>
 | 
					 | 
				
			||||||
						<Icon name='home' size='big' />
 | 
					 | 
				
			||||||
					</Menu.Item>
 | 
					 | 
				
			||||||
					<Menu.Item>
 | 
					 | 
				
			||||||
						<Icon name='circle' color={connected ? 'green' : 'red'} />
 | 
					 | 
				
			||||||
					</Menu.Item>
 | 
					 | 
				
			||||||
					<Menu.Menu position='right'>
 | 
					 | 
				
			||||||
						<Menu.Item position='right'>
 | 
					 | 
				
			||||||
							<Input transparent inverted placeholder='Search...' icon='search' />
 | 
					 | 
				
			||||||
						</Menu.Item>
 | 
											</Menu.Item>
 | 
				
			||||||
					</Menu.Menu>
 | 
											<Menu.Item as={Link} to='/'>
 | 
				
			||||||
				</Menu>
 | 
												<Icon name='home' size='big' />
 | 
				
			||||||
				<div style={{height: '70px', display: 'inline-block'}} />
 | 
											</Menu.Item>
 | 
				
			||||||
 | 
											<Menu.Item>
 | 
				
			||||||
 | 
												<Icon name='circle' color={connected && toolStatus ? 'green' : 'red'} />
 | 
				
			||||||
 | 
												{connected && toolStatus ? 'Connected' : 'Disconnected'}
 | 
				
			||||||
 | 
											</Menu.Item>
 | 
				
			||||||
 | 
										</Menu>
 | 
				
			||||||
 | 
										<div style={{height: '70px', display: 'inline-block'}} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										<Confirm
 | 
				
			||||||
 | 
											open={login.confirmLogout}
 | 
				
			||||||
 | 
											header='Logout'
 | 
				
			||||||
 | 
											onCancel={this.cancelLogout}
 | 
				
			||||||
 | 
											onConfirm={this.handleLogout}
 | 
				
			||||||
 | 
										/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				{toolData && user ?
 | 
										{login.token ?
 | 
				
			||||||
					<div>
 | 
											<div>
 | 
				
			||||||
						<Route exact path='/' render={props =>
 | 
												{toolData && user ?
 | 
				
			||||||
							<Categories {...props} data={toolData} />
 | 
													<div>
 | 
				
			||||||
						} />
 | 
														<Route exact path='/' render={props =>
 | 
				
			||||||
 | 
															<Categories {...props} data={toolData} />
 | 
				
			||||||
 | 
														} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						<Route exact path='/:category' render={props =>
 | 
														<Route exact path='/:category' render={props =>
 | 
				
			||||||
							<Category {...props} data={toolData} user={user} />
 | 
															<Category {...props} data={toolData} user={user} />
 | 
				
			||||||
						} />
 | 
														} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						<Route exact path='/:category/:id' render={props =>
 | 
														<Route exact path='/:category/:slug' render={props =>
 | 
				
			||||||
							<Tool {...props}
 | 
															<Tool {...props}
 | 
				
			||||||
								data={toolData}
 | 
																data={toolData}
 | 
				
			||||||
								user={user}
 | 
																user={user}
 | 
				
			||||||
								toolStatus={toolStatus}
 | 
																toolStatus={toolStatus}
 | 
				
			||||||
								requestInterlock={this.requestInterlock}
 | 
																requestInterlock={this.requestInterlock}
 | 
				
			||||||
							/>
 | 
															/>
 | 
				
			||||||
						} />
 | 
														} />
 | 
				
			||||||
					</div>
 | 
													</div>
 | 
				
			||||||
				:
 | 
												:
 | 
				
			||||||
					<Dimmer active>
 | 
													<Dimmer active>
 | 
				
			||||||
						<Loader>Loading</Loader>
 | 
														<Loader>Loading</Loader>
 | 
				
			||||||
					</Dimmer>
 | 
													</Dimmer>
 | 
				
			||||||
				}
 | 
												}
 | 
				
			||||||
			</div>
 | 
											</div>
 | 
				
			||||||
 | 
										:
 | 
				
			||||||
 | 
											<Container text>
 | 
				
			||||||
 | 
												<Form size='large' onSubmit={this.handleSubmit}>
 | 
				
			||||||
 | 
													{login.error ?
 | 
				
			||||||
 | 
														<Message visible error header='Invalid username / password!' />
 | 
				
			||||||
 | 
													:
 | 
				
			||||||
 | 
														<Message visible warning header='Please log in to access the tools!' />
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
													<Form.Field>
 | 
				
			||||||
 | 
														<label>Protospace Username</label>
 | 
				
			||||||
 | 
														<input name='username' placeholder='Username' value={login.username} onChange={this.handleChange} />
 | 
				
			||||||
 | 
													</Form.Field>
 | 
				
			||||||
 | 
													<Form.Field>
 | 
				
			||||||
 | 
														<label>Password</label>
 | 
				
			||||||
 | 
														<input name='password' type='password' value={login.password} onChange={this.handleChange} />
 | 
				
			||||||
 | 
													</Form.Field>
 | 
				
			||||||
 | 
													<Button type='submit'>Submit</Button>
 | 
				
			||||||
 | 
												</Form>
 | 
				
			||||||
 | 
											</Container>
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								:
 | 
				
			||||||
 | 
									<Dimmer active>
 | 
				
			||||||
 | 
										<Header inverted icon>
 | 
				
			||||||
 | 
											<Icon color='red' name='wifi' />
 | 
				
			||||||
 | 
											Please connect to the "Protospace" wifi
 | 
				
			||||||
 | 
										</Header>
 | 
				
			||||||
 | 
										<br />
 | 
				
			||||||
 | 
										<Button size='big' inverted icon labelPosition='left' onClick={this.handleRefresh}>
 | 
				
			||||||
 | 
											<Icon name='refresh' />
 | 
				
			||||||
 | 
											Refresh
 | 
				
			||||||
 | 
										</Button>
 | 
				
			||||||
 | 
									</Dimmer>
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,12 +23,12 @@ class Category extends Component {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
					<Item.Group link unstackable divided>
 | 
										<Item.Group link unstackable divided>
 | 
				
			||||||
						{category.tools.map((x, n) =>
 | 
											{category.tools.map((x, n) =>
 | 
				
			||||||
							<Item as={Link} to={'/' + category.slug + '/' + x.id} key={n}>
 | 
												<Item as={Link} to={'/' + category.slug + '/' + x.slug} key={n}>
 | 
				
			||||||
								<Item.Image size='tiny' src={x.photo} />
 | 
													<Item.Image size='tiny' src={x.photo} />
 | 
				
			||||||
								<Item.Content>
 | 
													<Item.Content>
 | 
				
			||||||
									<Item.Header>{x.name}</Item.Header>
 | 
														<Item.Header>{x.name}</Item.Header>
 | 
				
			||||||
									<Item.Description>
 | 
														<Item.Description>
 | 
				
			||||||
										{user.authorizedTools.includes(x.id) ?
 | 
															{user.profile.authorized_tools.includes(x.slug) ?
 | 
				
			||||||
											<Label color='green'>
 | 
																<Label color='green'>
 | 
				
			||||||
												<Icon name='check' /> Authorized
 | 
																	<Icon name='check' /> Authorized
 | 
				
			||||||
											</Label> : <Label color='red'>
 | 
																</Label> : <Label color='red'>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@ class Tool extends Component {
 | 
				
			|||||||
		} else if (status.action == 'arm') {
 | 
							} else if (status.action == 'arm') {
 | 
				
			||||||
			return { msg: 'Arming...', canArm: false, canDisarm: true, };
 | 
								return { msg: 'Arming...', canArm: false, canDisarm: true, };
 | 
				
			||||||
		} else if (status.action == 'disarm') {
 | 
							} else if (status.action == 'disarm') {
 | 
				
			||||||
			return { msg: 'Disarming...', canArm: true, canDisarm: false, };
 | 
								return { msg: 'Disarming...', canArm: false, canDisarm: false, };
 | 
				
			||||||
		} else if (status.state == 'off') {
 | 
							} else if (status.state == 'off') {
 | 
				
			||||||
			return { msg: 'Off.', canArm: true, canDisarm: false, };
 | 
								return { msg: 'Off.', canArm: true, canDisarm: false, };
 | 
				
			||||||
		} else if (status.state == 'armed') {
 | 
							} else if (status.state == 'armed') {
 | 
				
			||||||
@@ -33,16 +33,14 @@ class Tool extends Component {
 | 
				
			|||||||
		);
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const tool = category.tools.find(x =>
 | 
							const tool = category.tools.find(x =>
 | 
				
			||||||
			x.id.toString() === match.params.id
 | 
								x.slug === match.params.slug
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const status = toolStatus.find(x =>
 | 
							const status = toolStatus[match.params.slug] || null;
 | 
				
			||||||
			x.id.toString() === match.params.id
 | 
					 | 
				
			||||||
		) || null;
 | 
					 | 
				
			||||||
		const decodedStatus = this.decodeStatus(status);
 | 
							const decodedStatus = this.decodeStatus(status);
 | 
				
			||||||
		console.log(decodedStatus);
 | 
							console.log(decodedStatus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const approved = user.authorizedTools.includes(tool.id);
 | 
							const approved = user.profile.authorized_tools.includes(tool.slug);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return (
 | 
							return (
 | 
				
			||||||
			<div>
 | 
								<div>
 | 
				
			||||||
@@ -58,24 +56,25 @@ class Tool extends Component {
 | 
				
			|||||||
					<Segment>
 | 
										<Segment>
 | 
				
			||||||
						<Image src={tool.photo} size='medium' centered rounded />
 | 
											<Image src={tool.photo} size='medium' centered rounded />
 | 
				
			||||||
						<Segment textAlign='center' basic>
 | 
											<Segment textAlign='center' basic>
 | 
				
			||||||
							<p> Status: {decodedStatus.msg}</p>
 | 
												<p>Status: {decodedStatus.msg}</p>
 | 
				
			||||||
							<div>
 | 
												<div>
 | 
				
			||||||
							<Button color='green'
 | 
													<Button color='green'
 | 
				
			||||||
								disabled={!approved || !decodedStatus.canArm}
 | 
														disabled={!approved || !decodedStatus.canArm}
 | 
				
			||||||
								onClick={() => requestInterlock({toolId: tool.id, action: 'arm',})}
 | 
														onClick={() => requestInterlock({toolSlug: tool.slug, action: 'arm',})}
 | 
				
			||||||
							>
 | 
													>
 | 
				
			||||||
									<Icon name='lightning' /> Arm
 | 
															<Icon name='lightning' /> Arm
 | 
				
			||||||
							</Button>
 | 
													</Button>
 | 
				
			||||||
							<Button color='red'
 | 
													<Button color='red'
 | 
				
			||||||
								disabled={!approved || !decodedStatus.canDisarm}
 | 
														disabled={!approved || !decodedStatus.canDisarm}
 | 
				
			||||||
								onClick={() => requestInterlock({toolId: tool.id, action: 'disarm',})}
 | 
														onClick={() => requestInterlock({toolSlug: tool.slug, action: 'disarm',})}
 | 
				
			||||||
							>
 | 
													>
 | 
				
			||||||
									<Icon name='stop' /> Disarm
 | 
															<Icon name='stop' /> Disarm
 | 
				
			||||||
							</Button>
 | 
													</Button>
 | 
				
			||||||
 | 
													<br />
 | 
				
			||||||
 | 
													{approved || <Label basic color='red' pointing>
 | 
				
			||||||
 | 
														Not authorized! Please take the xyz course.
 | 
				
			||||||
 | 
													</Label>}
 | 
				
			||||||
							</div>
 | 
												</div>
 | 
				
			||||||
							{approved || <Label basic color='red' pointing>
 | 
					 | 
				
			||||||
								Not authorized! Please take the xyz course.
 | 
					 | 
				
			||||||
							</Label>}
 | 
					 | 
				
			||||||
						</Segment>
 | 
											</Segment>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						<Table basic='very' unstackable>
 | 
											<Table basic='very' unstackable>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@
 | 
				
			|||||||
  "author": "",
 | 
					  "author": "",
 | 
				
			||||||
  "license": "MIT",
 | 
					  "license": "MIT",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "axios": "^0.18.0",
 | 
				
			||||||
    "body-parser": "^1.18.2",
 | 
					    "body-parser": "^1.18.2",
 | 
				
			||||||
    "express": "^4.16.2",
 | 
					    "express": "^4.16.2",
 | 
				
			||||||
    "socket.io": "^2.0.4"
 | 
					    "socket.io": "^2.0.4"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,106 +1,20 @@
 | 
				
			|||||||
const express = require('express');
 | 
					const express = require('express');
 | 
				
			||||||
const bodyParser = require('body-parser');
 | 
					const bodyParser = require('body-parser');
 | 
				
			||||||
 | 
					const axios = require('axios');
 | 
				
			||||||
const app = express();
 | 
					const app = express();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AUTH_SERVER_URL = 'http://localhost:8000';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Enums
 | 
					// Enums
 | 
				
			||||||
const lockStates = {
 | 
					const lockStates = {
 | 
				
			||||||
	LOCK_OFF: 0,
 | 
						LOCK_OFF: 0,
 | 
				
			||||||
	LOCK_ARMED: 1,
 | 
						LOCK_PREARM: 1,
 | 
				
			||||||
	LOCK_ON_PRESSED: 2,
 | 
						LOCK_ARMED: 2,
 | 
				
			||||||
	LOCK_ON: 3,
 | 
						LOCK_ON_PRESSED: 3,
 | 
				
			||||||
	LOCK_OFF_PRESSED: 4,
 | 
						LOCK_ON: 4,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Hardcoded data - can only be changed by admin
 | 
					let toolStatus = null;
 | 
				
			||||||
const toolData = {
 | 
					 | 
				
			||||||
	categories: [
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name: 'Woodshop',
 | 
					 | 
				
			||||||
			slug: 'woodshop',
 | 
					 | 
				
			||||||
			info: 'Some info about the woodshop.',
 | 
					 | 
				
			||||||
			photo: 'https://i.imgur.com/RIm8aoG.jpg',
 | 
					 | 
				
			||||||
			tools: [
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					id: 0,
 | 
					 | 
				
			||||||
					name: 'White Bandsaw',
 | 
					 | 
				
			||||||
					photo: 'http://wiki.protospace.ca/images/thumb/f/f8/105.jpg/146px-105.jpg',
 | 
					 | 
				
			||||||
					notes: 'Requires training. Tighten tension lever prior to use / Loosen tension lever after use. Tension lever is on rear.',
 | 
					 | 
				
			||||||
					wikiId: 105,
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					id: 1,
 | 
					 | 
				
			||||||
					name: 'Jointer',
 | 
					 | 
				
			||||||
					photo: 'http://wiki.protospace.ca/images/thumb/6/6f/73.jpg/302px-73.jpg',
 | 
					 | 
				
			||||||
					notes: 'Boards must be ABSOLUTELY FREE of nails, staples, screws, or other metal. Check boards thoroughly each and every time, make no presumptions. One mistake will chip the blade and render it useless (drum blades are very expensive).',
 | 
					 | 
				
			||||||
					wikiId: 73,
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			],
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name: 'Metalshop',
 | 
					 | 
				
			||||||
			slug: 'metalshop',
 | 
					 | 
				
			||||||
			info: 'Some info about the metalshop.',
 | 
					 | 
				
			||||||
			photo: 'https://i.imgur.com/A2L2ACY.jpg',
 | 
					 | 
				
			||||||
			tools: [
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					id: 2,
 | 
					 | 
				
			||||||
					name: 'Tormach CNC',
 | 
					 | 
				
			||||||
					photo: 'http://wiki.protospace.ca/images/thumb/c/cf/49.jpg/320px-49.jpg',
 | 
					 | 
				
			||||||
					notes: 'Must complete Shop Safety, Lathe Training, 3D CAD Training, CAM Training, 1 on 1 Project.',
 | 
					 | 
				
			||||||
					wikiId: 49,
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					id: 3,
 | 
					 | 
				
			||||||
					name: 'Powerfist Bench Grinder',
 | 
					 | 
				
			||||||
					photo: 'http://wiki.protospace.ca/images/thumb/c/cd/39.jpg/298px-39.jpg',
 | 
					 | 
				
			||||||
					notes: 'A bench grinder is for grinding steels or deburring steel or aluminum (if fitted with a wire wheel).',
 | 
					 | 
				
			||||||
					wikiId: 39,
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			],
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	],
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Hardcoded data - can only be changed by admin
 | 
					 | 
				
			||||||
const users = [
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		username: "protospace",
 | 
					 | 
				
			||||||
		name: "Protospace User",
 | 
					 | 
				
			||||||
		authorizedTools: [1, 2],
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Hardcoded data - can only be changed by admin
 | 
					 | 
				
			||||||
const lockoutData = [
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		id: 0,
 | 
					 | 
				
			||||||
		mac: 'ABCDEF000000',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		id: 1,
 | 
					 | 
				
			||||||
		mac: '2C3AE843A15F',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		id: 2,
 | 
					 | 
				
			||||||
		mac: '2C3AE8439EAD',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		id: 3,
 | 
					 | 
				
			||||||
		mac: 'ABCDEF000003',
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Derived data - changes through use of system
 | 
					 | 
				
			||||||
let toolStatus = lockoutData.map(x => (
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		id: x.id,
 | 
					 | 
				
			||||||
		action: '',
 | 
					 | 
				
			||||||
		state: 'off',
 | 
					 | 
				
			||||||
		lastState: 'n/a',
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
console.log(toolStatus);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const server = app.listen(8080, () => {
 | 
					const server = app.listen(8080, () => {
 | 
				
			||||||
    console.log('Example app listening on port 8080!');
 | 
					    console.log('Example app listening on port 8080!');
 | 
				
			||||||
@@ -118,64 +32,59 @@ app.use((req, res, next) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
app.use(bodyParser.json());
 | 
					app.use(bodyParser.json());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.use('/', express.static('dist'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.get('/api/tooldata', (req, res) => {
 | 
					 | 
				
			||||||
	console.log('Request for tool data');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	res.setHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
	res.send(toolData);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.get('/api/user', (req, res) => {
 | 
					 | 
				
			||||||
	console.log('Request for user data');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	res.setHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
	res.send(users[0]);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.post('/api/lockout/:mac', (req, res) => {
 | 
					app.post('/api/lockout/:mac', (req, res) => {
 | 
				
			||||||
	const mac = req.params.mac;
 | 
						if (toolStatus) {
 | 
				
			||||||
	console.log('Request from MAC: ' + mac + ': ' + JSON.stringify(req.body));
 | 
							const mac = req.params.mac;
 | 
				
			||||||
 | 
							console.log('Request from MAC:', mac, ': ', JSON.stringify(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const lockout = lockoutData.find(x => x.mac === mac);
 | 
							const tmp = Object.entries(toolStatus).find(x => x[1].mac == mac)
 | 
				
			||||||
	if (!lockout) {
 | 
							const toolSlug = tmp ? tmp[0] : null;
 | 
				
			||||||
		res.sendStatus(404);
 | 
					
 | 
				
			||||||
 | 
							if (toolSlug) {
 | 
				
			||||||
 | 
								let clearAction = false;
 | 
				
			||||||
 | 
								let tool = toolStatus[toolSlug];
 | 
				
			||||||
 | 
								tool.lastState = tool.state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								switch (req.body.lockState) {
 | 
				
			||||||
 | 
									case lockStates.LOCK_OFF:
 | 
				
			||||||
 | 
										tool.state = 'off';
 | 
				
			||||||
 | 
										if (tool.action == 'disarm') clearAction = true;
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									case lockStates.LOCK_ARMED:
 | 
				
			||||||
 | 
										tool.state = 'armed';
 | 
				
			||||||
 | 
										if (tool.action == 'arm') clearAction = true;
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									case lockStates.LOCK_ON:
 | 
				
			||||||
 | 
										tool.state = 'on';
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Track in case state is switched from elsewhere
 | 
				
			||||||
 | 
								if (tool.state != tool.lastState) clearAction = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (clearAction) {
 | 
				
			||||||
 | 
									tool.action = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									console.log(tool);
 | 
				
			||||||
 | 
									io.sockets.emit('toolStatus', toolStatus);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								res.setHeader('Content-Type', 'application/json');
 | 
				
			||||||
 | 
								res.send(JSON.stringify({ action: tool.action }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								toolStatus[toolSlug] = tool;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								res.sendStatus(404);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							res.sendStatus(503);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	const toolIndex = toolStatus.findIndex(x => x.id === lockout.id);
 | 
					 | 
				
			||||||
	let tool = toolStatus[toolIndex];
 | 
					 | 
				
			||||||
	tool.lastState = tool.state;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	switch (req.body.lockState) {
 | 
					 | 
				
			||||||
		case lockStates.LOCK_OFF:
 | 
					 | 
				
			||||||
			tool.state = 'off';
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
		case lockStates.LOCK_ARMED:
 | 
					 | 
				
			||||||
			tool.state = 'armed';
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
		case lockStates.LOCK_ON:
 | 
					 | 
				
			||||||
			tool.state = 'on';
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
		default:
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	res.setHeader('Content-Type', 'application/json');
 | 
					 | 
				
			||||||
	res.send(JSON.stringify({ action: tool.action }));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (tool.state != tool.lastState) {
 | 
					 | 
				
			||||||
		tool.action = '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		console.log(toolStatus);
 | 
					 | 
				
			||||||
		io.sockets.emit('toolStatus', toolStatus);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	toolStatus[toolIndex] = tool;
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.get('*', (req, res) => {
 | 
					app.get('*', (req, res) => {
 | 
				
			||||||
	res.sendFile(path.resolve(__dirname, 'dist', 'index.html'));
 | 
						res.sendStatus(404);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Socket.io websocket stuff:
 | 
					// Socket.io websocket stuff:
 | 
				
			||||||
@@ -187,19 +96,43 @@ io.on('connection', socket => {
 | 
				
			|||||||
	socket.emit('toolStatus', toolStatus);
 | 
						socket.emit('toolStatus', toolStatus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	socket.on('requestInterlock', data => {
 | 
						socket.on('requestInterlock', data => {
 | 
				
			||||||
		console.log('Interlock change requested: ' + JSON.stringify(data));
 | 
							console.log('Interlock change requested: ', JSON.stringify(data));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const user = users.find(x => x.username === data.username);
 | 
							const token = data.token;
 | 
				
			||||||
		const toolId = data.change.toolId;
 | 
							const toolSlug = data.change.toolSlug;
 | 
				
			||||||
		const action = data.change.action;
 | 
							const action = data.change.action;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (user && user.authorizedTools.includes(data.change.toolId)) {
 | 
							axios.get(AUTH_SERVER_URL + '/user/', {
 | 
				
			||||||
			const toolIndex = toolStatus.findIndex(x => x.id === toolId);
 | 
								headers: {'Authorization': 'Token ' + token},
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
								.then(res => {
 | 
				
			||||||
 | 
									const profile = res.data[0].profile || null;
 | 
				
			||||||
 | 
									if (profile && profile.authorized_tools.includes(toolSlug)) {
 | 
				
			||||||
 | 
										toolStatus[toolSlug].action = action;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			toolStatus[toolIndex].action = action;
 | 
										console.log(profile.user, action, toolSlug);
 | 
				
			||||||
 | 
										io.sockets.emit('toolStatus', toolStatus);
 | 
				
			||||||
			console.log(toolStatus);
 | 
									}
 | 
				
			||||||
			io.sockets.emit('toolStatus', toolStatus);
 | 
								})
 | 
				
			||||||
		}
 | 
								.catch(err =>
 | 
				
			||||||
 | 
									console.log(err.message)
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setInterval(() => {
 | 
				
			||||||
 | 
						axios.get(AUTH_SERVER_URL + '/tooldata/')
 | 
				
			||||||
 | 
							.then(res => {
 | 
				
			||||||
 | 
								toolStatus = res.data.categories
 | 
				
			||||||
 | 
									.reduce((a, x) => a.concat(x.tools), [])
 | 
				
			||||||
 | 
									.reduce((a, x) => ({...a, [x.slug]: a[x.slug] ? {
 | 
				
			||||||
 | 
										...a[x.slug], mac: x.mac
 | 
				
			||||||
 | 
									} : {
 | 
				
			||||||
 | 
										mac: x.mac, action: '', state: 'off', lastState: 'n/a'
 | 
				
			||||||
 | 
									}}), toolStatus || {});
 | 
				
			||||||
 | 
								io.sockets.emit('toolStatus', toolStatus);
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.catch(err =>
 | 
				
			||||||
 | 
								console.log(err.message)
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					}, 10000);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,14 @@ async-limiter@~1.0.0:
 | 
				
			|||||||
  version "1.0.0"
 | 
					  version "1.0.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
 | 
					  resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					axios@^0.18.0:
 | 
				
			||||||
 | 
					  version "0.18.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
 | 
				
			||||||
 | 
					  integrity sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    follow-redirects "^1.3.0"
 | 
				
			||||||
 | 
					    is-buffer "^1.1.5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
backo2@1.0.2:
 | 
					backo2@1.0.2:
 | 
				
			||||||
  version "1.0.2"
 | 
					  version "1.0.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
 | 
					  resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
 | 
				
			||||||
@@ -119,7 +127,7 @@ debug@2.6.9:
 | 
				
			|||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    ms "2.0.0"
 | 
					    ms "2.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
debug@~3.1.0:
 | 
					debug@=3.1.0, debug@~3.1.0:
 | 
				
			||||||
  version "3.1.0"
 | 
					  version "3.1.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
 | 
					  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
@@ -237,6 +245,13 @@ finalhandler@1.1.1:
 | 
				
			|||||||
    statuses "~1.4.0"
 | 
					    statuses "~1.4.0"
 | 
				
			||||||
    unpipe "~1.0.0"
 | 
					    unpipe "~1.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					follow-redirects@^1.3.0:
 | 
				
			||||||
 | 
					  version "1.5.9"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.9.tgz#c9ed9d748b814a39535716e531b9196a845d89c6"
 | 
				
			||||||
 | 
					  integrity sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    debug "=3.1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
forwarded@~0.1.2:
 | 
					forwarded@~0.1.2:
 | 
				
			||||||
  version "0.1.2"
 | 
					  version "0.1.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
 | 
					  resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
 | 
				
			||||||
@@ -295,6 +310,11 @@ ipaddr.js@1.6.0:
 | 
				
			|||||||
  version "1.6.0"
 | 
					  version "1.6.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b"
 | 
					  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					is-buffer@^1.1.5:
 | 
				
			||||||
 | 
					  version "1.1.6"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
 | 
				
			||||||
 | 
					  integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
isarray@2.0.1:
 | 
					isarray@2.0.1:
 | 
				
			||||||
  version "2.0.1"
 | 
					  version "2.0.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
 | 
					  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user