Upgrade to material ui 4
Add user management and roles - TBA Menu Label Renames - TBA
This commit is contained in:
@ -10,14 +10,12 @@ import orange from '@material-ui/core/colors/orange';
|
||||
import red from '@material-ui/core/colors/red';
|
||||
import green from '@material-ui/core/colors/green';
|
||||
|
||||
import JssProvider from 'react-jss/lib/JssProvider';
|
||||
import { create } from 'jss';
|
||||
import { StylesProvider, jssPreset } from '@material-ui/styles';
|
||||
|
||||
import {
|
||||
MuiThemeProvider,
|
||||
createMuiTheme,
|
||||
createGenerateClassName,
|
||||
jssPreset,
|
||||
createMuiTheme
|
||||
} from '@material-ui/core/styles';
|
||||
|
||||
// Our theme
|
||||
@ -35,20 +33,17 @@ const theme = createMuiTheme({
|
||||
// JSS instance
|
||||
const jss = create(jssPreset());
|
||||
|
||||
// Class name generator.
|
||||
const generateClassName = createGenerateClassName();
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<JssProvider jss={jss} generateClassName={generateClassName}>
|
||||
<StylesProvider jss={jss}>
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<SnackbarNotification>
|
||||
<CssBaseline />
|
||||
<AppRouting />
|
||||
</SnackbarNotification>
|
||||
</MuiThemeProvider>
|
||||
</JssProvider>
|
||||
</StylesProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import NTPConfiguration from './containers/NTPConfiguration';
|
||||
import OTAConfiguration from './containers/OTAConfiguration';
|
||||
import APConfiguration from './containers/APConfiguration';
|
||||
import SignInPage from './containers/SignInPage';
|
||||
import UserConfiguration from './containers/UserConfiguration';
|
||||
import Security from './containers/Security';
|
||||
|
||||
class AppRouting extends Component {
|
||||
|
||||
@ -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="/user-configuration" component={UserConfiguration} />
|
||||
<AuthenticatedRoute exact path="/security" component={Security} />
|
||||
<Redirect to="/" />
|
||||
</Switch>
|
||||
</AuthenticationWrapper>
|
||||
|
@ -22,7 +22,7 @@ import SystemUpdateIcon from '@material-ui/icons/SystemUpdate';
|
||||
import AccessTimeIcon from '@material-ui/icons/AccessTime';
|
||||
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
|
||||
import SettingsInputAntennaIcon from '@material-ui/icons/SettingsInputAntenna';
|
||||
import PeopleIcon from '@material-ui/icons/People';
|
||||
import LockIcon from '@material-ui/icons/Lock';
|
||||
|
||||
import { APP_NAME } from '../constants/App';
|
||||
import { withAuthenticationContext } from '../authentication/Context.js';
|
||||
@ -112,31 +112,31 @@ class MenuAppBar extends React.Component {
|
||||
<ListItemIcon>
|
||||
<WifiIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="WiFi Configuration" />
|
||||
<ListItemText primary="WiFi Connection" />
|
||||
</ListItem>
|
||||
<ListItem button component={Link} to='/ap-configuration'>
|
||||
<ListItemIcon>
|
||||
<SettingsInputAntennaIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="AP Configuration" />
|
||||
<ListItemText primary="Access Point" />
|
||||
</ListItem>
|
||||
<ListItem button component={Link} to='/ntp-configuration'>
|
||||
<ListItemIcon>
|
||||
<AccessTimeIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="NTP Configuration" />
|
||||
<ListItemText primary="Network Time" />
|
||||
</ListItem>
|
||||
<ListItem button component={Link} to='/ota-configuration'>
|
||||
<ListItemIcon>
|
||||
<SystemUpdateIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="OTA Configuration" />
|
||||
<ListItemText primary="OTA Updates" />
|
||||
</ListItem>
|
||||
<ListItem button component={Link} to='/user-configuration'>
|
||||
<ListItem button component={Link} to='/security'>
|
||||
<ListItemIcon>
|
||||
<PeopleIcon />
|
||||
<LockIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="User Configuration" />
|
||||
<ListItemText primary="Security" />
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem button onClick={authenticationContext.signOut}>
|
||||
|
@ -16,7 +16,7 @@ function SectionContent(props) {
|
||||
const { children, classes, title } = props;
|
||||
return (
|
||||
<Paper className={classes.content}>
|
||||
<Typography variant="h4">
|
||||
<Typography variant="h6">
|
||||
{title}
|
||||
</Typography>
|
||||
{children}
|
||||
|
@ -7,17 +7,18 @@ import Typography from '@material-ui/core/Typography';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import SettingsInputAntennaIcon from '@material-ui/icons/SettingsInputAntenna';
|
||||
import DeviceHubIcon from '@material-ui/icons/DeviceHub';
|
||||
import ComputerIcon from '@material-ui/icons/Computer';
|
||||
|
||||
import {restComponent} from '../components/RestComponent';
|
||||
import { restComponent } from '../components/RestComponent';
|
||||
import SectionContent from '../components/SectionContent'
|
||||
|
||||
import * as Highlight from '../constants/Highlight';
|
||||
import { AP_STATUS_ENDPOINT } from '../constants/Endpoints';
|
||||
import { AP_STATUS_ENDPOINT } from '../constants/Endpoints';
|
||||
|
||||
const styles = theme => ({
|
||||
["apStatus_" + Highlight.SUCCESS]: {
|
||||
@ -42,40 +43,48 @@ class APStatus extends Component {
|
||||
this.props.loadData();
|
||||
}
|
||||
|
||||
apStatusHighlight(data){
|
||||
apStatusHighlight(data) {
|
||||
return data.active ? Highlight.SUCCESS : Highlight.IDLE;
|
||||
}
|
||||
|
||||
apStatus(data){
|
||||
apStatus(data) {
|
||||
return data.active ? "Active" : "Inactive";
|
||||
}
|
||||
|
||||
createListItems(data, classes){
|
||||
createListItems(data, classes) {
|
||||
return (
|
||||
<Fragment>
|
||||
<ListItem>
|
||||
<Avatar className={classes["apStatus_" + this.apStatusHighlight(data)]}>
|
||||
<SettingsInputAntennaIcon />
|
||||
</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar className={classes["apStatus_" + this.apStatusHighlight(data)]}>
|
||||
<SettingsInputAntennaIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Status" secondary={this.apStatus(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<Avatar>IP</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>IP</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="IP Address" secondary={data.ip_address} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<DeviceHubIcon />
|
||||
</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<DeviceHubIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="MAC Address" secondary={data.mac_address} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<ComputerIcon />
|
||||
</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<ComputerIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="AP Clients" secondary={data.station_num} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
@ -83,8 +92,8 @@ class APStatus extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderAPStatus(data, classes){
|
||||
return (
|
||||
renderAPStatus(data, classes) {
|
||||
return (
|
||||
<div>
|
||||
<List>
|
||||
<Fragment>
|
||||
@ -99,30 +108,30 @@ class APStatus extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data, fetched, errorMessage, classes } = this.props;
|
||||
const { data, fetched, errorMessage, classes } = this.props;
|
||||
|
||||
return (
|
||||
<SectionContent title="AP Status">
|
||||
{
|
||||
!fetched ?
|
||||
<div>
|
||||
<LinearProgress className={classes.fetching}/>
|
||||
<Typography variant="h4" className={classes.fetching}>
|
||||
Loading...
|
||||
!fetched ?
|
||||
<div>
|
||||
<LinearProgress className={classes.fetching} />
|
||||
<Typography variant="h4" className={classes.fetching}>
|
||||
Loading...
|
||||
</Typography>
|
||||
</div>
|
||||
:
|
||||
data ? this.renderAPStatus(data, classes)
|
||||
:
|
||||
<div>
|
||||
<Typography variant="h4" className={classes.fetching}>
|
||||
{errorMessage}
|
||||
</Typography>
|
||||
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
|
||||
Refresh
|
||||
</div>
|
||||
:
|
||||
data ? this.renderAPStatus(data, classes)
|
||||
:
|
||||
<div>
|
||||
<Typography variant="h4" className={classes.fetching}>
|
||||
{errorMessage}
|
||||
</Typography>
|
||||
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</SectionContent>
|
||||
)
|
||||
}
|
||||
|
@ -1,30 +1,27 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { USERS_ENDPOINT } from '../constants/Endpoints';
|
||||
import {restComponent} from '../components/RestComponent';
|
||||
import SectionContent from '../components/SectionContent';
|
||||
import { USERS_ENDPOINT } from '../constants/Endpoints';
|
||||
import { restComponent } from '../components/RestComponent';
|
||||
import ManageUsersForm from '../forms/ManageUsersForm';
|
||||
|
||||
class ManageUsers extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
this.props.loadData();
|
||||
this.props.loadData();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data, fetched, errorMessage } = this.props;
|
||||
return (
|
||||
<SectionContent title="Manage Users">
|
||||
<ManageUsersForm
|
||||
users={data}
|
||||
usersFetched={fetched}
|
||||
errorMessage={errorMessage}
|
||||
onSubmit={this.props.saveData}
|
||||
onReset={this.props.loadData}
|
||||
handleValueChange={this.props.handleValueChange}
|
||||
handleCheckboxChange={this.props.handleCheckboxChange}
|
||||
/>
|
||||
</SectionContent>
|
||||
<ManageUsersForm
|
||||
userData={data}
|
||||
userDataFetched={fetched}
|
||||
errorMessage={errorMessage}
|
||||
onSubmit={this.props.saveData}
|
||||
onReset={this.props.loadData}
|
||||
setData={this.props.setData}
|
||||
handleValueChange={this.props.handleValueChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import LinearProgress from '@material-ui/core/LinearProgress';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
@ -16,10 +17,10 @@ import TimerIcon from '@material-ui/icons/Timer';
|
||||
import UpdateIcon from '@material-ui/icons/Update';
|
||||
import AvTimerIcon from '@material-ui/icons/AvTimer';
|
||||
|
||||
import { isSynchronized, ntpStatusHighlight, ntpStatus } from '../constants/NTPStatus';
|
||||
import { isSynchronized, ntpStatusHighlight, ntpStatus } from '../constants/NTPStatus';
|
||||
import * as Highlight from '../constants/Highlight';
|
||||
import { unixTimeToTimeAndDate } from '../constants/TimeFormat';
|
||||
import { NTP_STATUS_ENDPOINT } from '../constants/Endpoints';
|
||||
import { NTP_STATUS_ENDPOINT } from '../constants/Endpoints';
|
||||
import { restComponent } from '../components/RestComponent';
|
||||
import SectionContent from '../components/SectionContent';
|
||||
|
||||
@ -51,52 +52,61 @@ class NTPStatus extends Component {
|
||||
this.props.loadData();
|
||||
}
|
||||
|
||||
createListItems(data, classes){
|
||||
createListItems(data, classes) {
|
||||
return (
|
||||
<Fragment>
|
||||
<ListItem>
|
||||
<Avatar className={classes["ntpStatus_" + ntpStatusHighlight(data)]}>
|
||||
<UpdateIcon />
|
||||
</Avatar>
|
||||
<ListItem >
|
||||
<ListItemAvatar>
|
||||
<Avatar className={classes["ntpStatus_" + ntpStatusHighlight(data)]}>
|
||||
<UpdateIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Status" secondary={ntpStatus(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
{ isSynchronized(data) &&
|
||||
{isSynchronized(data) &&
|
||||
<Fragment>
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<AccessTimeIcon />
|
||||
</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<AccessTimeIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Time Now" secondary={unixTimeToTimeAndDate(data.now)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<SwapVerticalCircleIcon />
|
||||
</Avatar>
|
||||
<ListItemText primary="Last Sync" secondary={data.last_sync > 0 ? unixTimeToTimeAndDate(data.last_sync) : "never" } />
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<SwapVerticalCircleIcon />
|
||||
</Avatar></ListItemAvatar>
|
||||
<ListItemText primary="Last Sync" secondary={data.last_sync > 0 ? unixTimeToTimeAndDate(data.last_sync) : "never"} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
</Fragment>
|
||||
}
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<DNSIcon />
|
||||
</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<DNSIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="NTP Server" secondary={data.server} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<TimerIcon />
|
||||
</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<TimerIcon />
|
||||
</Avatar></ListItemAvatar>
|
||||
<ListItemText primary="Sync Interval" secondary={moment.duration(data.interval, 'seconds').humanize()} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<AvTimerIcon />
|
||||
</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<AvTimerIcon />
|
||||
</Avatar></ListItemAvatar>
|
||||
<ListItemText primary="Uptime" secondary={moment.duration(data.uptime, 'seconds').humanize()} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
@ -104,8 +114,8 @@ class NTPStatus extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderNTPStatus(data, classes){
|
||||
return (
|
||||
renderNTPStatus(data, classes) {
|
||||
return (
|
||||
<div>
|
||||
<List>
|
||||
{this.createListItems(data, classes)}
|
||||
@ -118,30 +128,30 @@ class NTPStatus extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data, fetched, errorMessage, classes } = this.props;
|
||||
const { data, fetched, errorMessage, classes } = this.props;
|
||||
|
||||
return (
|
||||
<SectionContent title="NTP Status">
|
||||
{
|
||||
!fetched ?
|
||||
<div>
|
||||
<LinearProgress className={classes.fetching}/>
|
||||
<Typography variant="h4" className={classes.fetching}>
|
||||
Loading...
|
||||
!fetched ?
|
||||
<div>
|
||||
<LinearProgress className={classes.fetching} />
|
||||
<Typography variant="h4" className={classes.fetching}>
|
||||
Loading...
|
||||
</Typography>
|
||||
</div>
|
||||
:
|
||||
data ? this.renderNTPStatus(data, classes)
|
||||
:
|
||||
<div>
|
||||
<Typography variant="h4" className={classes.fetching}>
|
||||
{errorMessage}
|
||||
</Typography>
|
||||
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
|
||||
Refresh
|
||||
</div>
|
||||
:
|
||||
data ? this.renderNTPStatus(data, classes)
|
||||
:
|
||||
<div>
|
||||
<Typography variant="h4" className={classes.fetching}>
|
||||
{errorMessage}
|
||||
</Typography>
|
||||
<Button variant="contained" color="secondary" className={classes.button} onClick={this.props.loadData}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</SectionContent>
|
||||
)
|
||||
}
|
||||
|
@ -2,14 +2,14 @@ import React, { Component } from 'react';
|
||||
import MenuAppBar from '../components/MenuAppBar';
|
||||
import ManageUsers from './ManageUsers';
|
||||
|
||||
class UserConfiguration extends Component {
|
||||
class Security extends Component {
|
||||
render() {
|
||||
return (
|
||||
<MenuAppBar sectionTitle="User Configuration">
|
||||
<MenuAppBar sectionTitle="Security">
|
||||
<ManageUsers />
|
||||
</MenuAppBar>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default UserConfiguration
|
||||
export default Security
|
@ -8,6 +8,7 @@ import Typography from '@material-ui/core/Typography';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
||||
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
@ -63,9 +64,11 @@ class WiFiStatus extends Component {
|
||||
return (
|
||||
<Fragment>
|
||||
<ListItem>
|
||||
<Avatar className={classes["wifiStatus_" + connectionStatusHighlight(data)]}>
|
||||
<WifiIcon />
|
||||
</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar className={classes["wifiStatus_" + connectionStatusHighlight(data)]}>
|
||||
<WifiIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Connection Status" secondary={connectionStatus(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
@ -73,40 +76,52 @@ class WiFiStatus extends Component {
|
||||
isConnected(data) &&
|
||||
<Fragment>
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<SettingsInputAntennaIcon />
|
||||
</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<SettingsInputAntennaIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="SSID" secondary={data.ssid} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<Avatar>IP</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>IP</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="IP Address" secondary={data.local_ip} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<DeviceHubIcon />
|
||||
</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<DeviceHubIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="MAC Address" secondary={data.mac_address} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<Avatar>#</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>#</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Subnet Mask" secondary={data.subnet_mask} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<SettingsInputComponentIcon />
|
||||
</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<SettingsInputComponentIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Gateway IP" secondary={data.gateway_ip ? data.gateway_ip : "none"} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<DNSIcon />
|
||||
</Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<DNSIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="DNS Server IP" secondary={this.dnsServers(data)} />
|
||||
</ListItem>
|
||||
<Divider variant="inset" component="li" />
|
||||
|
@ -1,23 +1,26 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { ValidatorForm } from 'react-material-ui-form-validator';
|
||||
import { TextValidator, 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 Table from '@material-ui/core/Table';
|
||||
import TableBody from '@material-ui/core/TableBody';
|
||||
import TableCell from '@material-ui/core/TableCell';
|
||||
import TableFooter from '@material-ui/core/TableFooter';
|
||||
import TableHead from '@material-ui/core/TableHead';
|
||||
import TableRow from '@material-ui/core/TableRow';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
|
||||
import EditIcon from '@material-ui/icons/Edit';
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
|
||||
import SectionContent from '../components/SectionContent';
|
||||
import UserForm from './UserForm';
|
||||
|
||||
const styles = theme => ({
|
||||
loadingSettings: {
|
||||
@ -43,95 +46,198 @@ const styles = theme => ({
|
||||
margin: theme.spacing.unit,
|
||||
},
|
||||
table: {
|
||||
'& td, & th': {padding: theme.spacing.unit}
|
||||
}
|
||||
'& td, & th': { padding: theme.spacing.unit }
|
||||
},
|
||||
actions: {
|
||||
color: theme.palette.text.secondary,
|
||||
}
|
||||
});
|
||||
|
||||
function compareUsers(a, b) {
|
||||
if (a.username < b.username) {
|
||||
return -1;
|
||||
}
|
||||
if (a.username > b.username) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
class ManageUsersForm extends React.Component {
|
||||
|
||||
render() {
|
||||
const { classes, users, usersFetched, errorMessage, onSubmit, onReset } = this.props;
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
!usersFetched ?
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
createUser = () => {
|
||||
this.setState({
|
||||
creating: true,
|
||||
user: {
|
||||
username: "",
|
||||
password: "",
|
||||
roles: []
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
uniqueUsername = username => {
|
||||
return !this.props.userData.users.find(u => u.username === username);
|
||||
}
|
||||
|
||||
usersValid = username => {
|
||||
return !!this.props.userData.users.find(u => u.roles.includes("admin"));
|
||||
}
|
||||
|
||||
startEditingUser = user => {
|
||||
this.setState({
|
||||
creating: false,
|
||||
user
|
||||
});
|
||||
};
|
||||
|
||||
cancelEditingUser = () => {
|
||||
this.setState({
|
||||
user: undefined
|
||||
});
|
||||
}
|
||||
|
||||
sortedUsers(users) {
|
||||
return users.sort(compareUsers);
|
||||
}
|
||||
|
||||
doneEditingUser = () => {
|
||||
const { user } = this.state;
|
||||
const { userData } = this.props;
|
||||
let { users } = userData;
|
||||
users = users.filter(u => u.username !== user.username);
|
||||
users.push(user);
|
||||
this.props.setData({ ...userData, users });
|
||||
this.setState({
|
||||
user: undefined
|
||||
});
|
||||
};
|
||||
|
||||
handleUserValueChange = name => event => {
|
||||
const { user } = this.state;
|
||||
if (user) {
|
||||
this.setState({
|
||||
user: {
|
||||
...user, [name]: event.target.value
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, userData, userDataFetched, errorMessage, onSubmit, onReset, handleValueChange } = 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>
|
||||
|
||||
: users ?
|
||||
|
||||
<ValidatorForm onSubmit={onSubmit}>
|
||||
<Table className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Username</TableCell>
|
||||
<TableCell>Password</TableCell>
|
||||
<TableCell align="center">Role(s)</TableCell>
|
||||
<TableCell align="center">Action</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{users.users.map(user => (
|
||||
<TableRow key={user.username}>
|
||||
<TableCell component="th" scope="row">
|
||||
{user.username}
|
||||
</TableCell>
|
||||
<TableCell>{user.password}</TableCell>
|
||||
<TableCell align="center">
|
||||
<Chip label={user.role} className={classes.chip} />
|
||||
:
|
||||
userData ?
|
||||
user ?
|
||||
<UserForm
|
||||
user={user}
|
||||
creating={creating}
|
||||
roles={userData.roles}
|
||||
onDoneEditing={this.doneEditingUser}
|
||||
onCancelEditing={this.cancelEditingUser}
|
||||
handleValueChange={this.handleUserValueChange}
|
||||
uniqueUsername={this.uniqueUsername}
|
||||
/>
|
||||
:
|
||||
<ValidatorForm onSubmit={onSubmit}>
|
||||
<Table className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Username</TableCell>
|
||||
<TableCell align="center">Role(s)</TableCell>
|
||||
<TableCell align="center">Action</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{this.sortedUsers(userData.users).map(user => (
|
||||
<TableRow key={user.username}>
|
||||
<TableCell component="th" scope="row">
|
||||
{user.username}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{
|
||||
user.roles.map(role => (
|
||||
<Chip label={role} className={classes.chip} />
|
||||
))
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<IconButton aria-label="Delete">
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton aria-label="Edit" onClick={() => this.startEditingUser(user)}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>
|
||||
{
|
||||
!this.usersValid() &&
|
||||
<Typography variant="body1" color="error">
|
||||
You must have at least one admin user configured.
|
||||
</Typography>
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<IconButton aria-label="Delete">
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton aria-label="Edit">
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<Button variant="contained" color="secondary" className={classes.button} onClick={this.createUser}>
|
||||
Add User
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<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>
|
||||
|
||||
</TableFooter>
|
||||
</Table>
|
||||
<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}>
|
||||
<SectionContent title="Manage Users">
|
||||
<Typography variant="h4" className={classes.loadingSettingsDetails}>
|
||||
{errorMessage}
|
||||
</Typography>
|
||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onReset}>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</Button>
|
||||
</SectionContent>
|
||||
}
|
||||
</div>
|
||||
</SectionContent>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ManageUsersForm.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
users: PropTypes.object,
|
||||
usersFetched: PropTypes.bool.isRequired,
|
||||
userData: PropTypes.object,
|
||||
userDataFetched: PropTypes.bool.isRequired,
|
||||
errorMessage: PropTypes.string,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
onReset: PropTypes.func.isRequired,
|
||||
handleValueChange: PropTypes.func.isRequired,
|
||||
handleCheckboxChange: PropTypes.func.isRequired,
|
||||
setData: PropTypes.func.isRequired,
|
||||
handleValueChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ManageUsersForm);
|
||||
|
112
interface/src/forms/UserForm.js
Normal file
112
interface/src/forms/UserForm.js
Normal file
@ -0,0 +1,112 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
||||
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
|
||||
import PasswordValidator from '../components/PasswordValidator';
|
||||
|
||||
import Input from '@material-ui/core/Input';
|
||||
import InputLabel from '@material-ui/core/InputLabel';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
|
||||
const styles = theme => ({
|
||||
textField: {
|
||||
width: "100%"
|
||||
},
|
||||
checkboxControl: {
|
||||
width: "100%"
|
||||
},
|
||||
chips: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
chip: {
|
||||
marginRight: theme.spacing.unit,
|
||||
},
|
||||
button: {
|
||||
marginRight: theme.spacing.unit * 2,
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
}
|
||||
});
|
||||
|
||||
class UserForm extends React.Component {
|
||||
|
||||
componentWillMount() {
|
||||
ValidatorForm.addValidationRule('uniqueUsername', this.props.uniqueUsername);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, user, roles, creating, handleValueChange, onDoneEditing, onCancelEditing } = this.props;
|
||||
return (
|
||||
<ValidatorForm onSubmit={onDoneEditing}>
|
||||
<TextValidator
|
||||
validators={creating ? ['required', 'uniqueUsername', 'matchRegexp:^[a-zA-Z0-9_\\.]{1,24}$'] : []}
|
||||
errorMessages={creating ? ['Username is required', "That username already exists", "Must be 1-24 characters: alpha numberic, '_' or '.'"] : []}
|
||||
name="username"
|
||||
label="Username"
|
||||
className={classes.textField}
|
||||
value={user.username}
|
||||
disabled={!creating}
|
||||
onChange={handleValueChange('username')}
|
||||
margin="normal"
|
||||
/>
|
||||
<PasswordValidator
|
||||
validators={['required', 'matchRegexp:^.{0,64}$']}
|
||||
errorMessages={['Password is required', 'Password must be 64 characters or less']}
|
||||
name="password"
|
||||
label="Password"
|
||||
className={classes.textField}
|
||||
value={user.password}
|
||||
onChange={handleValueChange('password')}
|
||||
margin="normal"
|
||||
/>
|
||||
<FormControl className={classes.textField}>
|
||||
<InputLabel htmlFor="roles">Roles</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={user.roles}
|
||||
onChange={handleValueChange('roles')}
|
||||
input={<Input id="roles" />}
|
||||
renderValue={selected => (
|
||||
<div className={classes.chips}>
|
||||
{selected.map(value => (
|
||||
<Chip key={value} label={value} className={classes.chip} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{roles.map(name => (
|
||||
<MenuItem key={name} value={name}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button variant="contained" color="primary" className={classes.button} type="submit">
|
||||
Save
|
||||
</Button>
|
||||
<Button variant="contained" color="secondary" className={classes.button} onClick={onCancelEditing}>
|
||||
Back
|
||||
</Button>
|
||||
</ValidatorForm>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserForm.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
creating: PropTypes.bool.isRequired,
|
||||
roles: PropTypes.array.isRequired,
|
||||
onDoneEditing: PropTypes.func.isRequired,
|
||||
onCancelEditing: PropTypes.func.isRequired,
|
||||
uniqueUsername: PropTypes.func.isRequired,
|
||||
handleValueChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withStyles(styles)(UserForm);
|
Reference in New Issue
Block a user