Skip to content

Commit

Permalink
Authentication functional with peristent support
Browse files Browse the repository at this point in the history
  • Loading branch information
safchain committed Oct 26, 2019
1 parent 3cf9d8e commit 5adae8f
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 53 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@
"file-loader": "^4.2.0",
"history": "^4.10.1",
"html-webpack-plugin": "^3.2.0",
"image-webpack-loader": "^5.0.0",
"lodash.deburr": "^4.1.0",
"mocha": "^6.2.1",
"mui-datatables": "^2.12.2",
"mui-datatables": "^2.12.4",
"notistack": "^0.8.9",
"nyc": "^14.1.1",
"react": "^16.8.6",
Expand All @@ -48,13 +47,14 @@
"react-resize-observer": "^1.1.1",
"react-router-dom": "^5.1.2",
"react-sliding-pane": "^3.1.0",
"react-websocket": "^2.0.1",
"react-websocket": "^2.1.0",
"roboto-fontface": "^0.10.0",
"source-map-loader": "^0.2.4",
"style-loader": "^1.0.0",
"ts-debounce": "^1.0.0",
"ts-node": "^8.4.1",
"typescript": "^3.6.4",
"url-loader": "^2.2.0",
"webpack": "^4.39.2",
"webpack-cli": "^3.3.7",
"webpack-dev-server": "^3.8.0"
Expand Down
118 changes: 95 additions & 23 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import Drawer from '@material-ui/core/Drawer'
import AppBar from '@material-ui/core/AppBar'
import Toolbar from '@material-ui/core/Toolbar'
import IconButton from '@material-ui/core/IconButton'
import MenuIcon from '@material-ui/icons/Menu'
import Typography from '@material-ui/core/Typography'
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'
import Divider from '@material-ui/core/Divider'
Expand All @@ -38,12 +37,17 @@ import FormGroup from '@material-ui/core/FormGroup'
import FormControlLabel from '@material-ui/core/FormControlLabel'
import { withSnackbar, WithSnackbarProps } from 'notistack'
import { connect } from 'react-redux'
import AccountCircle from '@material-ui/icons/AccountCircle'
import MenuIcon from '@material-ui/icons/Menu'
import MenuItem from '@material-ui/core/MenuItem'
import Menu from '@material-ui/core/Menu'
import { withRouter } from 'react-router-dom'

import { AppStyles } from './Styles'
import { Topology, Node, NodeAttrs, LinkAttrs, LinkTagState, Link } from './Topology'
import { mainListItems, helpListItems } from './Menu'
import AutoCompleteInput from './AutoComplete'
import { AppState, selectNode, unselectNode, bumpRevision, session } from './Store'
import { AppState, selectNode, unselectNode, bumpRevision, session, closeSession } from './Store'
import SelectionPanel from './SelectionPanel'

import './App.css'
Expand All @@ -57,9 +61,11 @@ interface Props extends WithSnackbarProps {
classes: any
selectNode: typeof selectNode
unselectNode: typeof unselectNode
selection: Array<Node|Link>
selection: Array<Node | Link>
bumpRevision: typeof bumpRevision
session: session
closeSession: typeof closeSession
history: any
}

interface State {
Expand All @@ -69,6 +75,7 @@ interface State {
isNavOpen: boolean
linkTagStates: Map<string, LinkTagState>
suggestions: Array<string>
anchorEl: null | HTMLElement
}

class App extends React.Component<Props, State> {
Expand All @@ -79,6 +86,7 @@ class App extends React.Component<Props, State> {
state: State
refreshTopology: any
bumpRevision: typeof bumpRevision
checkAuthID: number

constructor(props) {
super(props)
Expand All @@ -89,19 +97,12 @@ class App extends React.Component<Props, State> {
contextMenuY: 0,
isNavOpen: false,
linkTagStates: new Map<string, LinkTagState>(),
suggestions: new Array<string>()
suggestions: new Array<string>(),
anchorEl: null
}

this.synced = false

this.onShowNodeContextMenu = this.onShowNodeContextMenu.bind(this)
this.onOpen = this.onOpen.bind(this)
this.onClose = this.onClose.bind(this)
this.onMessage = this.onMessage.bind(this)
this.onNodeSelected = this.onNodeSelected.bind(this)
this.onLayerLinkStateChange = this.onLayerLinkStateChange.bind(this)
this.onSearchChange = this.onSearchChange.bind(this)

this.refreshTopology = debounce(this._refreshTopology.bind(this), 300)

// we will refresh info each 1s
Expand All @@ -110,6 +111,16 @@ class App extends React.Component<Props, State> {

componentDidMount() {
//this.parseTopology(data)

this.checkAuthID = window.setInterval(() => {
this.checkAuth()
}, 2000)
}

componentWillUnmount() {
if (this.checkAuthID) {
window.clearInterval(this.checkAuthID)
}
}

fillSuggestions(node: Node, suggestions: Array<string>) {
Expand Down Expand Up @@ -314,7 +325,7 @@ class App extends React.Component<Props, State> {
}
}

onMessage(msg: string) {
onWebSocketMessage(msg: string) {
var data: { Type: string, Obj: any } = JSON.parse(msg)
switch (data.Type) {
case "SyncReply":
Expand Down Expand Up @@ -380,14 +391,33 @@ class App extends React.Component<Props, State> {
}
}

onClose() {
onWebSocketClose() {
if (this.synced) {
this.notify("Disconnected", "error")
} else {
this.notify("Not connected", "error")
}

this.synced = false

// check if still authenticated
this.checkAuth()
}

checkAuth() {
const requestOptions = {
method: 'GET',
headers: {
'X-Auth-Token': this.props.session.token
}
}

return fetch(`${this.props.session.endpoint}/api/status`, requestOptions)
.then(response => {
if (response.status !== 200) {
this.logout()
}
})
}

sendMessage(data) {
Expand All @@ -399,7 +429,7 @@ class App extends React.Component<Props, State> {
this.sendMessage(msg)
}

onOpen() {
onWebSocketOpen() {
if (!this.tc) {
return
}
Expand Down Expand Up @@ -473,14 +503,28 @@ class App extends React.Component<Props, State> {
return url.toString()
}

openProfileMenu(event: React.MouseEvent<HTMLElement>) {
this.setState({ anchorEl: event.currentTarget })
}

closeProfileMenu() {
this.setState({ anchorEl: null })
}

logout() {
this.props.closeSession()
this.props.history.push("/login")
}

render() {
const { classes } = this.props

return (
<div className={classes.app}>
<CssBaseline />
<Websocket ref={node => this.websocket = node} url={this.subscriberURL()} onOpen={this.onOpen}
onMessage={this.onMessage} onClose={this.onClose} reconnectIntervalInMilliSeconds={2500} />
<Websocket ref={node => this.websocket = node} url={this.subscriberURL()} onOpen={this.onWebSocketOpen.bind(this)}
onMessage={this.onWebSocketMessage.bind(this)} onClose={this.onWebSocketClose.bind(this)}
reconnectIntervalInMilliSeconds={2500} />
<AppBar position="absolute" className={clsx(classes.appBar, this.state.isNavOpen && classes.appBarShift)}>
<Toolbar className={classes.toolbar}>
<IconButton
Expand All @@ -495,7 +539,34 @@ class App extends React.Component<Props, State> {
<img src={Logo} alt="logo" />
</Typography>
<div className={classes.search}>
<AutoCompleteInput placeholder="metadata value" suggestions={this.state.suggestions} onChange={this.onSearchChange} />
<AutoCompleteInput placeholder="metadata value" suggestions={this.state.suggestions} onChange={this.onSearchChange.bind(this)} />
</div>
<div className={classes.grow} />
<div>
<IconButton
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={this.openProfileMenu.bind(this)}
color="inherit">
<AccountCircle fontSize="large" />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={this.state.anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={this.state.anchorEl !== null}
onClose={this.closeProfileMenu.bind(this)}>
<MenuItem onClick={this.logout.bind(this)}>Logout</MenuItem>
</Menu>
</div>
</Toolbar>
</AppBar>
Expand All @@ -519,8 +590,8 @@ class App extends React.Component<Props, State> {
<div className={classes.appBarSpacer} />
<Container maxWidth="xl" className={classes.container}>
<Topology className={classes.topology} ref={node => this.tc = node} nodeAttrs={this.nodeAttrs} linkAttrs={this.linkAttrs}
onNodeSelected={this.onNodeSelected} sortNodesFnc={this.sortNodesFnc}
onShowNodeContextMenu={this.onShowNodeContextMenu} weightTitles={this.weightTitles()}
onNodeSelected={this.onNodeSelected.bind(this)} sortNodesFnc={this.sortNodesFnc}
onShowNodeContextMenu={this.onShowNodeContextMenu.bind(this)} weightTitles={this.weightTitles()}
groupBy={config.groupBy} />
</Container>
<Container className={classes.rightPanel}>
Expand All @@ -538,7 +609,7 @@ class App extends React.Component<Props, State> {
<FormGroup>
{Array.from(this.state.linkTagStates.keys()).map((key) => (
<FormControlLabel key={key} control={
<Checkbox value={key} color="primary" onChange={this.onLayerLinkStateChange}
<Checkbox value={key} color="primary" onChange={this.onLayerLinkStateChange.bind(this)}
checked={this.state.linkTagStates.get(key) === LinkTagState.Visible}
indeterminate={this.state.linkTagStates.get(key) === LinkTagState.EventBased} />
}
Expand All @@ -562,7 +633,8 @@ export const mapStateToProps = (state: AppState) => ({
export const mapDispatchToProps = ({
selectNode,
unselectNode,
bumpRevision
bumpRevision,
closeSession
})

export default withStyles(AppStyles)(connect(mapStateToProps, mapDispatchToProps)(withSnackbar(App)))
export default withStyles(AppStyles)(connect(mapStateToProps, mapDispatchToProps)(withSnackbar(withRouter(App))))
27 changes: 17 additions & 10 deletions src/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,13 @@ import * as React from "react"
import { connect } from 'react-redux'
import Container from '@material-ui/core/Container'
import Typography from '@material-ui/core/Typography'
import Avatar from '@material-ui/core/Avatar'
import Button from '@material-ui/core/Button'
import CssBaseline from '@material-ui/core/CssBaseline'
import TextField from '@material-ui/core/TextField'
import { withStyles } from '@material-ui/core/styles'
import FormControlLabel from '@material-ui/core/FormControlLabel'
import Checkbox from '@material-ui/core/Checkbox'
import { AppState, registerSession } from './Store'
import { AppState, openSession, session } from './Store'
import { withRouter } from 'react-router-dom'

import { LoginStyles } from './Styles'
Expand All @@ -35,9 +34,10 @@ import Logo from '../assets/Logo-large.png'

interface Props {
classes: any
registerSession: typeof registerSession
openSession: typeof openSession
history: any
location: any
session: session
}

interface State {
Expand All @@ -46,6 +46,7 @@ interface State {
password: string
submitted: boolean
failure: boolean
persistent: boolean
}

class Login extends React.Component<Props, State> {
Expand All @@ -55,14 +56,13 @@ class Login extends React.Component<Props, State> {
constructor(props) {
super(props)

console.log(window.location)

this.state = {
endpoint: `${window.location.protocol}//${window.location.hostname}:8082`,
endpoint: "",
username: "",
password: "",
submitted: false,
failure: false
failure: false,
persistent: false
}
}

Expand All @@ -78,6 +78,9 @@ class Login extends React.Component<Props, State> {
case "password":
this.setState({ password: value })
break
case "persistent":
this.setState({ persistent: Boolean(value) })
break
}
}

Expand Down Expand Up @@ -113,7 +116,7 @@ class Login extends React.Component<Props, State> {
})
.then(data => {
if (data) {
this.props.registerSession(this.state.endpoint, this.state.username, data.Token, data.Permissions)
this.props.openSession(this.state.endpoint, this.state.username, data.Token, data.Permissions, this.state.persistent)

const { from } = this.props.location.state || { from: { pathname: "/" } }
this.props.history.push(from)
Expand Down Expand Up @@ -151,7 +154,7 @@ class Login extends React.Component<Props, State> {
name="endpoint"
autoComplete="endpoint"
autoFocus
value={this.state.endpoint}
value={this.props.session.endpoint}
onChange={this.handleChange.bind(this)}
/>
{this.state.submitted && !this.state.endpoint &&
Expand Down Expand Up @@ -192,6 +195,9 @@ class Login extends React.Component<Props, State> {
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
name="persistent"
value={true}
onChange={this.handleChange.bind(this)}
/>
<Button
type="submit"
Expand All @@ -210,10 +216,11 @@ class Login extends React.Component<Props, State> {
}

export const mapStateToProps = (state: AppState) => ({
session: state.session
})

export const mapDispatchToProps = ({
registerSession
openSession
})

export default withStyles(LoginStyles)(connect(mapStateToProps, mapDispatchToProps)(withRouter(Login)))
Loading

0 comments on commit 5adae8f

Please sign in to comment.