add security form, begin work on routing
This commit is contained in:
		| @@ -1 +1 @@ | ||||
| REACT_APP_ENDPOINT_ROOT=http://192.168.0.16/rest/ | ||||
| REACT_APP_ENDPOINT_ROOT=http://192.168.0.11/rest/ | ||||
|   | ||||
| @@ -31,7 +31,7 @@ class AppRouting extends Component { | ||||
|           <AuthenticatedRoute exact path="/ap-configuration" component={APConfiguration} /> | ||||
|           <AuthenticatedRoute exact path="/ntp-configuration" component={NTPConfiguration} /> | ||||
|           <AuthenticatedRoute exact path="/ota-configuration" component={OTAConfiguration} /> | ||||
|           <AuthenticatedRoute exact path="/security" component={Security} /> | ||||
|           <AuthenticatedRoute exact path="/security/*" component={Security} /> | ||||
|           <Redirect to="/" /> | ||||
|         </Switch> | ||||
|       </AuthenticationWrapper> | ||||
|   | ||||
| @@ -137,7 +137,7 @@ class MenuAppBar extends React.Component { | ||||
|             </ListItemIcon> | ||||
|             <ListItemText primary="OTA Updates" /> | ||||
|           </ListItem> | ||||
|           <ListItem button component={Link} to='/security'> | ||||
|           <ListItem button component={Link} to='/security/'> | ||||
|             <ListItemIcon> | ||||
|               <LockIcon /> | ||||
|             </ListItemIcon> | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import React, { Component } from 'react'; | ||||
| import { USERS_ENDPOINT } from '../constants/Endpoints'; | ||||
| import { restComponent } from '../components/RestComponent'; | ||||
| import ManageUsersForm from '../forms/ManageUsersForm'; | ||||
| import SectionContent from '../components/SectionContent'; | ||||
|  | ||||
| class ManageUsers extends Component { | ||||
|  | ||||
| @@ -13,15 +14,17 @@ class ManageUsers extends Component { | ||||
|   render() { | ||||
|     const { data, fetched, errorMessage } = this.props; | ||||
|     return ( | ||||
|       <ManageUsersForm | ||||
|         userData={data} | ||||
|         userDataFetched={fetched} | ||||
|         errorMessage={errorMessage} | ||||
|         onSubmit={this.props.saveData} | ||||
|         onReset={this.props.loadData} | ||||
|         setData={this.props.setData} | ||||
|         handleValueChange={this.props.handleValueChange} | ||||
|       /> | ||||
|       <SectionContent title="Manage Users"> | ||||
|         <ManageUsersForm | ||||
|           userData={data} | ||||
|           userDataFetched={fetched} | ||||
|           errorMessage={errorMessage} | ||||
|           onSubmit={this.props.saveData} | ||||
|           onReset={this.props.loadData} | ||||
|           setData={this.props.setData} | ||||
|           handleValueChange={this.props.handleValueChange} | ||||
|         /> | ||||
|       </SectionContent> | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -1,15 +1,35 @@ | ||||
| import React, { Component } from 'react'; | ||||
| import { Redirect, Switch } from 'react-router-dom' | ||||
|  | ||||
| import Tabs from '@material-ui/core/Tabs'; | ||||
| import Tab from '@material-ui/core/Tab'; | ||||
|  | ||||
| import AuthenticatedRoute from '../authentication/AuthenticatedRoute'; | ||||
| import MenuAppBar from '../components/MenuAppBar'; | ||||
| import ManageUsers from './ManageUsers'; | ||||
| import SecuritySettings from './SecuritySettings'; | ||||
|  | ||||
| class Security extends Component { | ||||
|  | ||||
|   handleTabChange = (event, path) => { | ||||
|     this.props.history.push(path); | ||||
|   }; | ||||
|  | ||||
|   render() { | ||||
|     return ( | ||||
|         <MenuAppBar sectionTitle="Security"> | ||||
|           <ManageUsers /> | ||||
|         </MenuAppBar> | ||||
|       <MenuAppBar sectionTitle="Security"> | ||||
|         <Tabs value={this.props.match.url} onChange={this.handleTabChange} indicatorColor="primary" textColor="primary" variant="fullWidth"> | ||||
|           <Tab value="/security/users" label="Manage Users" /> | ||||
|           <Tab value="/security/settings" label="Security Settings" /> | ||||
|         </Tabs> | ||||
|         <Switch> | ||||
|           <AuthenticatedRoute exact={true} path="/security/users" component={ManageUsers} /> | ||||
|           <AuthenticatedRoute exact={true} path="/security/settings" component={SecuritySettings} /> | ||||
|           <Redirect to="/security/users" /> | ||||
|         </Switch> | ||||
|       </MenuAppBar> | ||||
|     ) | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default Security | ||||
| export default Security; | ||||
|   | ||||
							
								
								
									
										32
									
								
								interface/src/containers/SecuritySettings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								interface/src/containers/SecuritySettings.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| import React, { Component } from 'react'; | ||||
|  | ||||
| import { USERS_ENDPOINT } from '../constants/Endpoints'; | ||||
| import { restComponent } from '../components/RestComponent'; | ||||
| import SecuritySettingsForm from '../forms/SecuritySettingsForm'; | ||||
| import SectionContent from '../components/SectionContent'; | ||||
|  | ||||
| class SecuritySettings extends Component { | ||||
|  | ||||
|   componentDidMount() { | ||||
|     this.props.loadData(); | ||||
|   } | ||||
|  | ||||
|   render() { | ||||
|     const { data, fetched, errorMessage } = this.props; | ||||
|     return ( | ||||
|       <SectionContent title="Security Settings"> | ||||
|         <SecuritySettingsForm | ||||
|           securitySettings={data} | ||||
|           securitySettingsFetched={fetched} | ||||
|           errorMessage={errorMessage} | ||||
|           onSubmit={this.props.saveData} | ||||
|           onReset={this.props.loadData} | ||||
|           handleValueChange={this.props.handleValueChange} | ||||
|         /> | ||||
|       </SectionContent> | ||||
|     ) | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
| export default restComponent(USERS_ENDPOINT, SecuritySettings); | ||||
| @@ -21,7 +21,6 @@ import CloseIcon from '@material-ui/icons/Close'; | ||||
| import CheckIcon from '@material-ui/icons/Check'; | ||||
| import IconButton from '@material-ui/core/IconButton'; | ||||
|  | ||||
| import SectionContent from '../components/SectionContent'; | ||||
| import UserForm from './UserForm'; | ||||
| import { withAuthenticationContext } from '../authentication/Context'; | ||||
|  | ||||
| @@ -138,108 +137,101 @@ class ManageUsersForm extends React.Component { | ||||
|     const { classes, userData, userDataFetched, errorMessage, onReset } = this.props; | ||||
|     const { user, creating } = this.state; | ||||
|     return ( | ||||
|       <SectionContent title="Manage Users"> | ||||
|         { | ||||
|           !userDataFetched ? | ||||
|             <div className={classes.loadingSettings}> | ||||
|               <LinearProgress className={classes.loadingSettingsDetails} /> | ||||
|               <Typography variant="h4" className={classes.loadingSettingsDetails}> | ||||
|                 Loading... | ||||
|               </Typography> | ||||
|             </div> | ||||
|             : | ||||
|             userData ? | ||||
|               <Fragment> | ||||
|                 <ValidatorForm onSubmit={this.onSubmit}> | ||||
|                   <Table className={classes.table}> | ||||
|                     <TableHead> | ||||
|                       <TableRow> | ||||
|                         <TableCell>Username</TableCell> | ||||
|                         <TableCell align="center">Admin?</TableCell> | ||||
|                         <TableCell /> | ||||
|                       </TableRow> | ||||
|                     </TableHead> | ||||
|                     <TableBody> | ||||
|                       {userData.users.sort(compareUsers).map(user => ( | ||||
|                         <TableRow key={user.username}> | ||||
|                           <TableCell component="th" scope="row"> | ||||
|                             {user.username} | ||||
|                           </TableCell> | ||||
|                           <TableCell align="center"> | ||||
|                             { | ||||
|                               user.admin ? <CheckIcon /> : <CloseIcon /> | ||||
|                             } | ||||
|                           </TableCell> | ||||
|                           <TableCell align="center"> | ||||
|                             <IconButton aria-label="Delete" onClick={() => this.removeUser(user)}> | ||||
|                               <DeleteIcon /> | ||||
|                             </IconButton> | ||||
|                             <IconButton aria-label="Edit" onClick={() => this.startEditingUser(user)}> | ||||
|                               <EditIcon /> | ||||
|                             </IconButton> | ||||
|                           </TableCell> | ||||
|                         </TableRow> | ||||
|                       ))} | ||||
|                     </TableBody> | ||||
|                     <TableFooter> | ||||
|                       <TableRow> | ||||
|                         <TableCell colSpan={2} /> | ||||
|                         <TableCell align="center"> | ||||
|                           <Button variant="contained" color="secondary" className={classes.button} onClick={this.createUser}> | ||||
|                             Add User | ||||
|                           </Button> | ||||
|                         </TableCell> | ||||
|                       </TableRow> | ||||
|                     </TableFooter> | ||||
|                   </Table> | ||||
|                   { | ||||
|                     this.noAdminConfigured() && | ||||
|                     <Typography component="div" variant="body1"> | ||||
|                       <Box bgcolor="error.main" color="error.contrastText" p={2} m={1}> | ||||
|                         You must have at least one admin user configured. | ||||
|                       </Box> | ||||
|                     </Typography> | ||||
|                   } | ||||
|                   <Button variant="contained" color="primary" className={classes.button} type="submit" disabled={this.noAdminConfigured()}> | ||||
|                     Save | ||||
|                   </Button> | ||||
|                   <Button variant="contained" color="secondary" className={classes.button} onClick={onReset}> | ||||
|                     Reset | ||||
|       		        </Button> | ||||
|                 </ValidatorForm> | ||||
|                 { | ||||
|                   user && | ||||
|  | ||||
|                   <UserForm | ||||
|                     user={user} | ||||
|                     creating={creating} | ||||
|                     onDoneEditing={this.doneEditingUser} | ||||
|                     onCancelEditing={this.cancelEditingUser} | ||||
|                     handleValueChange={this.handleUserValueChange} | ||||
|                     handleCheckboxChange={this.handleUserCheckboxChange} | ||||
|                     uniqueUsername={this.uniqueUsername} | ||||
|                   /> | ||||
|  | ||||
|                 } | ||||
|               </Fragment> | ||||
|               : | ||||
|               <SectionContent title="Manage Users"> | ||||
|                 <Typography variant="h4" className={classes.loadingSettingsDetails}> | ||||
|                   {errorMessage} | ||||
|       !userDataFetched ? | ||||
|         <div className={classes.loadingSettings}> | ||||
|           <LinearProgress className={classes.loadingSettingsDetails} /> | ||||
|           <Typography variant="h4" className={classes.loadingSettingsDetails}> | ||||
|             Loading... | ||||
|           </Typography> | ||||
|         </div> | ||||
|         : | ||||
|         userData ? | ||||
|           <Fragment> | ||||
|             <ValidatorForm onSubmit={this.onSubmit}> | ||||
|               <Table className={classes.table}> | ||||
|                 <TableHead> | ||||
|                   <TableRow> | ||||
|                     <TableCell>Username</TableCell> | ||||
|                     <TableCell align="center">Admin?</TableCell> | ||||
|                     <TableCell /> | ||||
|                   </TableRow> | ||||
|                 </TableHead> | ||||
|                 <TableBody> | ||||
|                   {userData.users.sort(compareUsers).map(user => ( | ||||
|                     <TableRow key={user.username}> | ||||
|                       <TableCell component="th" scope="row"> | ||||
|                         {user.username} | ||||
|                       </TableCell> | ||||
|                       <TableCell align="center"> | ||||
|                         { | ||||
|                           user.admin ? <CheckIcon /> : <CloseIcon /> | ||||
|                         } | ||||
|                       </TableCell> | ||||
|                       <TableCell align="center"> | ||||
|                         <IconButton aria-label="Delete" onClick={() => this.removeUser(user)}> | ||||
|                           <DeleteIcon /> | ||||
|                         </IconButton> | ||||
|                         <IconButton aria-label="Edit" onClick={() => this.startEditingUser(user)}> | ||||
|                           <EditIcon /> | ||||
|                         </IconButton> | ||||
|                       </TableCell> | ||||
|                     </TableRow> | ||||
|                   ))} | ||||
|                 </TableBody> | ||||
|                 <TableFooter> | ||||
|                   <TableRow> | ||||
|                     <TableCell colSpan={2} /> | ||||
|                     <TableCell align="center"> | ||||
|                       <Button variant="contained" color="secondary" onClick={this.createUser}> | ||||
|                         Add User | ||||
|                       </Button> | ||||
|                     </TableCell> | ||||
|                   </TableRow> | ||||
|                 </TableFooter> | ||||
|               </Table> | ||||
|               { | ||||
|                 this.noAdminConfigured() && | ||||
|                 <Typography component="div" variant="body1"> | ||||
|                   <Box bgcolor="error.main" color="error.contrastText" p={2} m={2}> | ||||
|                     You must have at least one admin user configured. | ||||
|                   </Box> | ||||
|                 </Typography> | ||||
|                 <Button variant="contained" color="secondary" className={classes.button} onClick={onReset}> | ||||
|                   Reset | ||||
|       		      </Button> | ||||
|               </SectionContent> | ||||
|         } | ||||
|       </SectionContent> | ||||
|               } | ||||
|               <Button variant="contained" color="primary" className={classes.button} type="submit" disabled={this.noAdminConfigured()}> | ||||
|                 Save | ||||
|               </Button> | ||||
|               <Button variant="contained" color="secondary" className={classes.button} onClick={onReset}> | ||||
|                 Reset | ||||
|       		    </Button> | ||||
|             </ValidatorForm> | ||||
|             { | ||||
|               user && | ||||
|               <UserForm | ||||
|                 user={user} | ||||
|                 creating={creating} | ||||
|                 onDoneEditing={this.doneEditingUser} | ||||
|                 onCancelEditing={this.cancelEditingUser} | ||||
|                 handleValueChange={this.handleUserValueChange} | ||||
|                 handleCheckboxChange={this.handleUserCheckboxChange} | ||||
|                 uniqueUsername={this.uniqueUsername} | ||||
|               /> | ||||
|             } | ||||
|           </Fragment> | ||||
|           : | ||||
|           <div className={classes.loadingSettings}> | ||||
|             <Typography variant="h4" className={classes.loadingSettingsDetails}> | ||||
|               {errorMessage} | ||||
|             </Typography> | ||||
|             <Button variant="contained" color="secondary" className={classes.button} onClick={onReset}> | ||||
|               Reset | ||||
|       		  </Button> | ||||
|           </div> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
| ManageUsersForm.propTypes = { | ||||
|   authenticationContext: PropTypes.object.isRequired, | ||||
|   classes: PropTypes.object.isRequired, | ||||
|   userData: PropTypes.object, | ||||
|   userDataFetched: PropTypes.bool.isRequired, | ||||
| @@ -247,7 +239,8 @@ ManageUsersForm.propTypes = { | ||||
|   onSubmit: PropTypes.func.isRequired, | ||||
|   onReset: PropTypes.func.isRequired, | ||||
|   setData: PropTypes.func.isRequired, | ||||
|   handleValueChange: PropTypes.func.isRequired | ||||
|   handleValueChange: PropTypes.func.isRequired, | ||||
|   authenticationContext: PropTypes.object.isRequired, | ||||
| }; | ||||
|  | ||||
| export default withAuthenticationContext(withStyles(styles)(ManageUsersForm)); | ||||
|   | ||||
							
								
								
									
										97
									
								
								interface/src/forms/SecuritySettingsForm.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								interface/src/forms/SecuritySettingsForm.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { ValidatorForm } from 'react-material-ui-form-validator'; | ||||
|  | ||||
| import { withStyles } from '@material-ui/core/styles'; | ||||
| import Button from '@material-ui/core/Button'; | ||||
| import LinearProgress from '@material-ui/core/LinearProgress'; | ||||
| import Typography from '@material-ui/core/Typography'; | ||||
| import Box from '@material-ui/core/Box'; | ||||
|  | ||||
| import PasswordValidator from '../components/PasswordValidator'; | ||||
| import { withAuthenticationContext } from '../authentication/Context'; | ||||
|  | ||||
| const styles = theme => ({ | ||||
|   loadingSettings: { | ||||
|     margin: theme.spacing.unit, | ||||
|   }, | ||||
|   loadingSettingsDetails: { | ||||
|     margin: theme.spacing.unit * 4, | ||||
|     textAlign: "center" | ||||
|   }, | ||||
|   textField: { | ||||
|     width: "100%" | ||||
|   }, | ||||
|   button: { | ||||
|     marginRight: theme.spacing.unit * 2, | ||||
|     marginTop: theme.spacing.unit * 2, | ||||
|   } | ||||
| }); | ||||
|  | ||||
| class SecuritySettingsForm extends React.Component { | ||||
|  | ||||
|   onSubmit = () => { | ||||
|     this.props.onSubmit(); | ||||
|     this.props.authenticationContext.refresh(); | ||||
|   } | ||||
|  | ||||
|   render() { | ||||
|     const { classes, securitySettingsFetched, securitySettings, errorMessage, handleValueChange, onReset } = this.props; | ||||
|     return ( | ||||
|       !securitySettingsFetched ? | ||||
|         <div className={classes.loadingSettings}> | ||||
|           <LinearProgress className={classes.loadingSettingsDetails} /> | ||||
|           <Typography variant="h4" className={classes.loadingSettingsDetails}> | ||||
|             Loading... | ||||
|           </Typography> | ||||
|         </div> | ||||
|         : | ||||
|         securitySettings ? | ||||
|           <ValidatorForm onSubmit={this.onSubmit} ref="SecuritySettingsForm"> | ||||
|             <PasswordValidator | ||||
|               validators={['required', 'matchRegexp:^.{0,64}$']} | ||||
|               errorMessages={['JWT Secret Required', 'JWT Secret must be 64 characters or less']} | ||||
|               name="jwt_secret" | ||||
|               label="JWT Secret" | ||||
|               className={classes.textField} | ||||
|               value={securitySettings.jwt_secret} | ||||
|               onChange={handleValueChange('jwt_secret')} | ||||
|               margin="normal" | ||||
|             /> | ||||
|             <Typography component="div" variant="body1"> | ||||
|               <Box bgcolor="primary.main" color="primary.contrastText" p={2} m={2}> | ||||
|                 If you modify the JWT Secret, all users will be logged out. | ||||
|               </Box> | ||||
|             </Typography> | ||||
|             <Button variant="contained" color="primary" className={classes.button} type="submit"> | ||||
|               Save | ||||
|             </Button> | ||||
|             <Button variant="contained" color="secondary" className={classes.button} onClick={onReset}> | ||||
|               Reset | ||||
|       		  </Button> | ||||
|           </ValidatorForm> | ||||
|           : | ||||
|           <div className={classes.loadingSettings}> | ||||
|             <Typography variant="h4" className={classes.loadingSettingsDetails}> | ||||
|               {errorMessage} | ||||
|             </Typography> | ||||
|             <Button variant="contained" color="secondary" className={classes.button} onClick={onReset}> | ||||
|               Reset | ||||
|       		  </Button> | ||||
|           </div> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| SecuritySettingsForm.propTypes = { | ||||
|   classes: PropTypes.object.isRequired, | ||||
|   securitySettingsFetched: PropTypes.bool.isRequired, | ||||
|   securitySettings: PropTypes.object, | ||||
|   errorMessage: PropTypes.string, | ||||
|   onSubmit: PropTypes.func.isRequired, | ||||
|   onReset: PropTypes.func.isRequired, | ||||
|   handleValueChange: PropTypes.func.isRequired, | ||||
|   authenticationContext: PropTypes.object.isRequired, | ||||
| }; | ||||
|  | ||||
| export default withAuthenticationContext(withStyles(styles)(SecuritySettingsForm)); | ||||
| @@ -44,7 +44,7 @@ class UserForm extends React.Component { | ||||
|     return ( | ||||
|       <ValidatorForm onSubmit={onDoneEditing} ref={this.formRef}> | ||||
|         <Dialog onClose={onCancelEditing} aria-labelledby="user-form-dialog-title" open={true} scroll="paper"> | ||||
|           <DialogTitle id="user-form-dialog-title">{creating ? 'Create' : 'Modify'} User</DialogTitle> | ||||
|           <DialogTitle id="user-form-dialog-title">{creating ? 'Add' : 'Modify'} User</DialogTitle> | ||||
|           <DialogContent dividers={true}> | ||||
|             <TextValidator | ||||
|               validators={creating ? ['required', 'uniqueUsername', 'matchRegexp:^[a-zA-Z0-9_\\.]{1,24}$'] : []} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user