diff --git a/jams-react-client/build_deploy_jams_client.sh b/jams-react-client/build_deploy_jams_client.sh index fcd2b78046f4902b823266f0002a88340ca3e2fb..56b8f5940533082a0c3941ef0765ba617f31d92c 100755 --- a/jams-react-client/build_deploy_jams_client.sh +++ b/jams-react-client/build_deploy_jams_client.sh @@ -20,7 +20,7 @@ sed -i 's/material-dashboard-react\///g' $WEBAPP"index.html" cd .. #mvn clean package -DskipTests -mvn package -DskipTests +mvn package cd $JAMS -java -jar jams-launcher.jar +java -jar jams-launcher.jar 8080 server.pem server.key diff --git a/jams-react-client/src/globalUrls.js b/jams-react-client/src/globalUrls.js index 0e712677a16d4955f716e7e6cdfe7cb010a5cefe..e31c392a2dcd1e929d31398038d499f8cdbc2b58 100644 --- a/jams-react-client/src/globalUrls.js +++ b/jams-react-client/src/globalUrls.js @@ -17,6 +17,9 @@ const api_path_delete_auth_user_revoke = '/api/auth/user'; const api_path_delete_admin_device_revoke = '/api/admin/device'; const api_path_delete_auth_device_revoke = '/api/auth/device'; const api_path_rename_device = '/api/auth/device'; +const api_path_get_list_group = '/api/admin/group'; +const api_path_delete_group = '/api/admin/group'; +const api_path_put_update_group = '/api/admin/group'; const api_path_get_server_status = '/api/info'; const api_path_get_post_configuration_auth_service = '/api/configuration/authservice'; const api_path_get_post_configuration_global_settings = '/api/configuration/globalsettings'; @@ -26,6 +29,7 @@ const api_path_get_subscription_status = '/api/admin/subscription'; const api_path_get_directories = '/api/auth/directories'; const api_path_get_needs_update = '/api/admin/update'; const api_path_get_start_update = '/api/admin/update'; +const api_path_post_create_group = '/api/admin/group'; const api_path_post_create_user = '/api/admin/user'; const api_path_get_auth_user = '/api/auth/user'; const api_path_get_admin_user = '/api/admin/user'; @@ -37,6 +41,7 @@ const api_path_put_update_user_profile = '/api/admin/directory/entry'; const api_path_get_user_search = '/api/admin/users'; const api_path_get_auth_contacts = '/api/auth/contacts'; const api_path_delete_auth_contacts = '/api/auth/contacts'; +const api_path_blueprints = '/api/admin/policy'; module.exports = { uri, @@ -77,5 +82,10 @@ module.exports = { api_path_put_update_user_profile, api_path_get_user_search, api_path_get_auth_contacts, - api_path_delete_auth_contacts + api_path_delete_auth_contacts, + api_path_delete_group, + api_path_get_list_group, + api_path_post_create_group, + api_path_put_update_group, + api_path_blueprints } \ No newline at end of file diff --git a/jams-react-client/src/views/Groups/EditGroup.js b/jams-react-client/src/views/Groups/EditGroup.js new file mode 100644 index 0000000000000000000000000000000000000000..3f7cfa323bf814446e6756fba0a2053e14c16e9d --- /dev/null +++ b/jams-react-client/src/views/Groups/EditGroup.js @@ -0,0 +1,242 @@ +import React from "react"; +import { useHistory } from 'react-router-dom'; +import classnames from "classnames"; + +// @material-ui/core components +import { makeStyles } from "@material-ui/core/styles"; +import InputLabel from "@material-ui/core/InputLabel"; +// core components +import Grid from '@material-ui/core/Grid'; +import GridItem from "components/Grid/GridItem.js"; +import GridContainer from "components/Grid/GridContainer.js"; +import CustomInput from "components/CustomInput/CustomInput.js"; +import Button from "components/CustomButtons/Button.js"; +import Card from "components/Card/Card.js"; +import CardHeader from "components/Card/CardHeader.js"; +import CardIcon from "components/Card/CardIcon.js"; +import CardAvatar from "components/Card/CardAvatar.js"; +import CardBody from "components/Card/CardBody.js"; +import CardFooter from "components/Card/CardFooter.js"; +import FormControl from '@material-ui/core/FormControl'; +import Input from '@material-ui/core/Input'; +import InputAdornment from '@material-ui/core/InputAdornment'; + +import Table from "@material-ui/core/Table"; +import TableHead from '@material-ui/core/TableHead'; +import TableRow from "@material-ui/core/TableRow"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; + +import Select from "@material-ui/core/Select"; + +import { hexToRgb, blackColor } from "assets/jss/material-dashboard-react.js"; + +import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline'; +import EditIcon from '@material-ui/icons/Edit'; +import PeopleOutlineIcon from '@material-ui/icons/PeopleOutline'; + +import axios from "axios" +import configApiCall from "../../api" +import { + api_path_get_list_group, + api_path_put_update_group, + api_path_get_auth_contacts +} from "../../globalUrls" + +import dashboardStyle from "assets/jss/material-dashboard-react/views/dashboardStyle.js"; +import devicesStyle from "assets/jss/material-dashboard-react/components/devicesStyle.js"; + +import TemporaryDrawer from "components/Drawer/Drawer" + +const styles = ()=> ( { + ...devicesStyle, + ...dashboardStyle, + root: { + flexGrow: 1 + }, + cardCategoryWhite: { + color: "rgba(255,255,255,.62)", + margin: "0", + fontSize: "14px", + marginTop: "0", + marginBottom: "0" + }, + cardTitleWhite: { + color: "#FFFFFF", + marginTop: "0px", + minHeight: "auto", + fontWeight: "300", + fontFamily: "'Roboto', 'Helvetica', 'Arial', sans-serif", + marginBottom: "3px", + textDecoration: "none" + }, + input: { + display: 'none', + }, + profileAsBackground: { + backgroundSize: "100% 100%", + width: "80px", + height: "80px", + }, + centerIconMiddle: { + position:"relative", + top: "20px", + left: "15px" + }, + dialogPaper: { + minHeight: '60vh', + maxHeight: '60vh', + minWidth: '80vh', + maxWidth: '80vh' + } +}); + +const useStyles = makeStyles(styles); + +export default function EditGroup(props) { + const classes = useStyles(); + const history = useHistory(); + const [groupExists, setGroupExists] = React.useState(false) + + const [name, setName] = React.useState("") + const [groupMembers, setGroupMembers] = React.useState([]) + + const [openDrawer, setOpenDrawer] = React.useState(false); + + const getAllGroupMembers = () => { + /* + TODO: Include the username of the user of witch we want to display contacts + at the moment the admin sees his contacts in each user profile he visits + */ + // axios(configApiCall(api_path_get_list_group, 'GET', {"groupName": props.groupName}, null)).then((response) => { + axios('../groups.json').then((response) => { + response.data.map((group) => { + if(group.name == props.groupName){ + setGroupMembers(group["groupMembers"]) + } + }) + }).catch((error) => { + console.log("Error fetching group members of: " + props.groupName + " " + error) + }) + } + + React.useEffect(()=>{ + setName(props.groupName) + // axios(configApiCall(api_path_get_list_group, 'GET', {"groupName": props.groupName}, null)).then((response) => { + axios('../groups.json').then((response) => { + response.data.map((group) => { + if(group.name == props.groupName){ + setGroupMembers(group["groupMembers"]) + } + }) + }).catch((error) => { + console.log("Error fetching group members of: " + props.groupName + " " + error) + }) + }, []) + + + // const getUserDetails = () => { + // axio + // } + + const handleGroupUpdate = (data) => { + props.setName(data.name) + props.setGroupMembers(data.groupMembers) + } + + const handleUpdateGroup = () => { + const data = { + 'name': name, + 'groupMembers': groupMembers + } + //TODO: Add blueprint to data to update the blueprint using the blueprintName + + axios(configApiCall(api_path_put_update_group, 'PUT', data, null)).then((response) => { + handleGroupUpdate(data); + }).catch((error) => { + console.log("Error updating group: " + error) + }) + } + + const tableCellClasses = classnames(classes.tableCell); + + return( + <div> + <TemporaryDrawer openDrawer={openDrawer} setOpenDrawer={setOpenDrawer} getAllContacts={getAllGroupMembers} direction="right"/> + <GridContainer> + <GridItem xs={12} sm={12} md={4}> + <Card profile> + <CardHeader color="info" stats icon> + <CardIcon color="info"> + <EditIcon /> + </CardIcon> + <p className={classes.cardCategory}>Edit group</p> + <h3 className={classes.cardTitle}>{props.groupName}</h3> + </CardHeader> + <CardBody profile> + <div className={classes.root}> + <Grid container spacing={2}> + <Grid item xs={12} sm={12} md={12}> + <FormControl className={classes.margin} size="medium" error={props.groupNameExits}> + <Input + id="name" + placeholder={props.groupName} + startAdornment={ + <InputAdornment position="start"> + <PeopleOutlineIcon /> + </InputAdornment> + } + onChange={e => { + setName(e.target.value); + props.initCheckGroupNameExists(e.target.value) + if(!props.groupNameExits){ + handleUpdateGroup() + } + }} + /> + </FormControl> + </Grid> + <Grid item xs={12} sm={12} md={12}> + <FormControl className={classes.margin} fullWidth> + <Select + labelId="demo-simple-select-label" + fullWidth + value={props.selectedBlueprint.value} + onChange={props.handleBlueprintsChange} + variant="outlined" + children={props.blueprintsOptionsItems} + /> + </FormControl> + </Grid> + </Grid> + </div> + </CardBody> + </Card> + </GridItem> + <GridItem xs={12} sm={12} md={12}> + <Button color="primary" onClick={() => {setOpenDrawer(true)}}><AddCircleOutlineIcon /> Add user to {props.groupName}</Button> + <Table className={classes.table}> + <TableHead> + <TableRow> + <TableCell>Username</TableCell> + <TableCell align="right">Action</TableCell> + </TableRow> + </TableHead> + <TableBody> + {groupMembers.map(user => + <TableRow key={user} className={classes.tableRow}> + <TableCell className={tableCellClasses}> + {user} + </TableCell> + <TableCell align="right" className={classes.tableActions}> + <Button color="primary">Remove user</Button> + </TableCell> + </TableRow> + ) } + </TableBody> + </Table> + </GridItem> + </GridContainer> + </div> + ); +} \ No newline at end of file diff --git a/jams-react-client/src/views/Groups/Groups.js b/jams-react-client/src/views/Groups/Groups.js index 851a83e6872fbddb242b4e49d7f98217d3998853..69b50306929dabda06d9d8b63b25db4b516f135c 100644 --- a/jams-react-client/src/views/Groups/Groups.js +++ b/jams-react-client/src/views/Groups/Groups.js @@ -1,19 +1,55 @@ -import React from "react"; +import React, {useEffect, useState, useCallback} from "react"; +import { useHistory } from 'react-router-dom'; // @material-ui/core components import { makeStyles } from "@material-ui/core/styles"; import InputLabel from "@material-ui/core/InputLabel"; // core components +import Grid from '@material-ui/core/Grid'; import GridItem from "components/Grid/GridItem.js"; import GridContainer from "components/Grid/GridContainer.js"; -import CustomInput from "components/CustomInput/CustomInput.js"; import Button from "components/CustomButtons/Button.js"; import Card from "components/Card/Card.js"; -import CardHeader from "components/Card/CardHeader.js"; import CardAvatar from "components/Card/CardAvatar.js"; import CardBody from "components/Card/CardBody.js"; import CardFooter from "components/Card/CardFooter.js"; +import Divider from '@material-ui/core/Divider'; +import CustomInput from "components/CustomInput/CustomInput.js"; + +import IconButton from '@material-ui/core/IconButton'; +import DeleteOutlineIcon from '@material-ui/icons/DeleteOutline'; +import EditOutlinedIcon from '@material-ui/icons/EditOutlined'; +import Search from "@material-ui/icons/Search"; +import PeopleOutlineIcon from '@material-ui/icons/PeopleOutline'; +import MailOutlineIcon from '@material-ui/icons/MailOutline'; +import PersonIcon from '@material-ui/icons/Person'; + +import Select from "@material-ui/core/Select"; + +import * as tool from "../../tools"; +import axios from "axios"; +import configApiCall from "api.js"; +import auth from 'auth.js' +import { api_path_post_create_group, api_path_get_list_group, api_path_delete_group, api_path_blueprints } from "globalUrls"; -import noProfilePicture from "assets/img/faces/no-profile-picture.png"; +import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline'; +import KeyboardReturnIcon from '@material-ui/icons/KeyboardReturn'; +import EditGroup from "views/Groups/EditGroup"; + +import FormGroup from '@material-ui/core/FormGroup'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import FormControl from '@material-ui/core/FormControl'; +import Input from '@material-ui/core/Input'; +import InputAdornment from '@material-ui/core/InputAdornment'; + +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; + +import { debounce } from "lodash"; + +import LinearProgress from '@material-ui/core/LinearProgress'; const styles = { cardCategoryWhite: { @@ -38,48 +74,289 @@ const useStyles = makeStyles(styles); export default function Groups() { const classes = useStyles(); - return ( - <div> - <GridContainer> - <GridItem xs={12} sm={12} md={4}> - <Card profile> - <CardAvatar profile> - <a href="#pablo" onClick={e => e.preventDefault()}> - <img src={noProfilePicture} alt="..." /> - </a> - </CardAvatar> - <CardBody profile> - <h6 className={classes.cardCategory}>CEO</h6> - <h4 className={classes.cardTitle}>Cyrille Béraud</h4> - <p className={classes.description}> - 13 devices - </p> - <Button color="primary" round> - Edit - </Button> - </CardBody> - </Card> - </GridItem> - <GridItem xs={12} sm={12} md={4}> - <Card profile> - <CardAvatar profile> - <a href="#pablo" onClick={e => e.preventDefault()}> - <img src={noProfilePicture} alt="..." /> - </a> - </CardAvatar> - <CardBody profile> - <h6 className={classes.cardCategory}>CEO</h6> - <h4 className={classes.cardTitle}>Christophe Villemaire</h4> - <p className={classes.description}> - 10 devices - </p> - <Button color="primary" round> - Edit - </Button> - </CardBody> - </Card> - </GridItem> - </GridContainer> - </div> - ); + const history = useHistory(); + const [groups, setGroups] = React.useState([]) + const [selectedGroupName, setSelectedGroupName] = React.useState("") + const [loading, setLoading] = React.useState(false) + const [progress, setProgress] = React.useState(0); + const [searchValue, setSearchValue] = React.useState(null) + + const [blueprints, setBlueprints] = React.useState([]) + + const [openCreate, setOpenCreate] = React.useState(false) + const [groupName, setGroupName] = React.useState('') + const [groupNameExits, setGroupNameExits] = React.useState(false) + + const [removedGroup, setRemovedGroup] = React.useState() + const [openRemoveDialog, setOpenRemoveDialog] = React.useState() + + const handleRemoveGroup = (groupRemovedName) => { + setRemovedGroup(groupRemovedName) + setOpenRemoveDialog(true) + } + + const removeGroup = () => { + axios(configApiCall(api_path_delete_group, 'DELETE', {"queryString": removedGroup}, null)).then((response)=>{ + console.log("Successfully deleted " + removedGroup) + }).catch((error) =>{ + console.log("Could not delete " + removedGroup + " " + error) + }); + setOpenRemoveDialog(false); + history.push('/admin/groups') + } + + useEffect(() => { + setLoading(true) + const timer = setInterval(() => { + setProgress((oldProgress) => { + if (oldProgress === 100) { + return 0; + } + const diff = Math.random() * 10; + return Math.min(oldProgress + diff, 100); + }); + }, 500); + // axios(configApiCall(api_path_get_list_group, 'GET', {"groupName":"*"}, null)).then((response)=>{ + axios('../groups.json').then((response) => { + let allGroups = response.data + allGroups.map((group) => { + group.display = "" + }) + setGroups(allGroups) + setLoading(false) + }).catch((error) =>{ + console.log(error); + if(error.response.status == 401){ + auth.authenticated = false + history.push('/') + } + }); + // axios(configApiCall(api_path_blueprints, 'GET', {"groupName":"*"}, null)).then((response)=>{ + axios('../blueprints.json').then((response) => { + setBlueprints(response.data) + }).catch((error) =>{ + console.log(error); + if(error.response.status == 401){ + auth.authenticated = false + history.push('/') + } + }); + return () => { + clearInterval(timer); + }; + }, []); + + const [selectedGroup, setSelectedGroup] = useState(false); + + const redirectToGroup = (e, name) => { + e.preventDefault() + setSelectedGroup(true); + setSelectedGroupName(name) + } + + const redirectToGroupList = (e) => { + e.preventDefault() + setSelectedGroup(false); + history.push('/admin/groups') + } + + const handleCheckGroupNameExists = (searchGroupNameValue) => { + axios(configApiCall(api_path_get_list_group, 'GET', {"groupName": searchGroupNameValue}, null)).then((response)=>{ + if(response.data == "[]"){ + setGroupNameExits(true) + } + }).catch((error) => { + console.log("Error checking for existing groups: " + error) + }) + } + + const initCheckGroupNameExists = useCallback(debounce((searchGroupNameValue) => handleCheckGroupNameExists(searchGroupNameValue), 500), []) + + const handleCloseCreate = () => { + setOpenCreate(false); + }; + + const handleCreateGroup = () => { + + const data = { + "groupname": groupName, + "groupMembers": {}, + "blueprint": "default" + } + + axios(configApiCall(api_path_post_create_group, 'POST', data, null)).then(() => { + console.log("Successfully create " + groupName) + }).catch((error) => { + console.log("Error creating group: " + error) + }) + setOpenCreate(false); + history.push('/admin/groups') + } + + + const getGroupsOptions = () => { + let blueprintsOptions = [] + let index = 0 + blueprints.map((blueprint) => { + blueprintsOptions.push({ value: index, label: blueprint.blueprintName}) + index += 1 + }) + return blueprintsOptions + } + + const blueprintsOptionsItems = tool.buildSelectMenuItems(getGroupsOptions()); + + const [selectedBlueprint, setSelectedBlueprint] = useState({ value: 0, label: ""}) + + const handleBlueprintsChange = (e) => { + setSelectedBlueprint(getGroupsOptions()[e.target.value]) + } + + if(selectedGroup && auth.hasAdminScope()){ + return( + <div> + <Button variant="contained" color="info" href="#contained-buttons" onClick={redirectToGroupList}> + <KeyboardReturnIcon /> + </Button> + <EditGroup + groupName={selectedGroupName} + initCheckGroupNameExists={initCheckGroupNameExists} + groupNameExits={groupNameExits} + blueprintsOptionsItems={blueprintsOptionsItems} + selectedBlueprint={selectedBlueprint} + handleBlueprintsChange={handleBlueprintsChange} + /> + </div> + ) + } else { + return ( + <div> + <Dialog + open={openCreate} + onClose={handleCloseCreate} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + <DialogTitle id="alert-dialog-title">{"Create group"}</DialogTitle> + <DialogContent> + <DialogContentText id="alert-dialog-description"> + <Grid container spacing={2}> + <Grid item xs={12} sm={12} md={12}> + <FormControl className={classes.margin} size="medium" error={groupNameExits}> + <InputLabel htmlFor="groupName">Group name</InputLabel> + <Input + id="groupName" + placeholder={groupName} + startAdornment={ + <InputAdornment position="start"> + <PeopleOutlineIcon /> + </InputAdornment> + } + onChange={e => { + const searchGroupNameValue = e.target.value; + setGroupName(searchGroupNameValue); + initCheckGroupNameExists(groupName); + }} + /> + </FormControl> + </Grid> + <Grid item xs={12} sm={12} md={12}> + <Select + labelId="demo-simple-select-label" + fullWidth + value={selectedBlueprint.value} + onChange={handleBlueprintsChange} + variant="outlined" + children={blueprintsOptionsItems} + /> + </Grid> + </Grid> + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={handleCloseCreate} color="danger"> + Cancel + </Button> + <Button onClick={handleCreateGroup} color="primary" autoFocus> + Create + </Button> + </DialogActions> + </Dialog> + <Dialog + open={openRemoveDialog} + onClose={() => setOpenRemoveDialog(false)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + <DialogTitle id="alert-dialog-title">{"Remove group"}</DialogTitle> + <DialogContent> + <DialogContentText id="alert-dialog-description"> + Are you sure you want to delete <strong>{removedGroup}</strong> ? + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={() => setOpenRemoveDialog(false)} color="primary"> + Cancel + </Button> + <Button onClick={removeGroup} color="danger" autoFocus> + Remove + </Button> + </DialogActions> + </Dialog> + <GridContainer> + <GridItem xs={12} sm={12} md={12}> + { + <Button variant="contained" color="primary" href="#contained-buttons" onClick={e => setOpenCreate(true)}> + <AddCircleOutlineIcon /> Create group + </Button> + } + <div className={classes.searchWrapper}> + <CustomInput + formControlProps={{ + className: classes.margin + " " + classes.search + }} + inputProps={{ + placeholder: "Search groups ...", + inputProps: { + "aria-label": "Search groups" + }, + onKeyUp: (e) => setSearchValue(e.target.value), + }} + /> + <Search /> + <div className={classes.loading}> + {loading && <LinearProgress variant="determinate" value={progress} />} + </div> + </div> + </GridItem> + { + groups.filter((data)=>{ + if(searchValue == null) + return data + else if((data.name != null ) && data.name.toLowerCase().includes(searchValue.toLowerCase()) + ){ + return data + } + }).map(group => + <GridItem xs={12} sm={12} md={2} key={group.username} style={{display: group.display}}> + <Card profile> + <CardBody profile> + <h3 className={classes.cardTitle}>{group.name}</h3> + <ul> + <li><PersonIcon fontSize='small' style={{ marginRight: "10px"}}/><strong style={{ marginRight: "5px"}}>{group.groupMembers.length}</strong>users</li> + <li><MailOutlineIcon fontSize='small' style={{ marginRight: "10px"}}/><strong style={{ marginRight: "5px"}}>Blueprint</strong>{group.blueprint}</li> + </ul> + </CardBody> + <CardFooter> + <IconButton color="primary" onClick={ (e) => redirectToGroup(e, group.name)}><EditOutlinedIcon /></IconButton> + <IconButton color="secondary" onClick={ () => {handleRemoveGroup(group.name)}}><DeleteOutlineIcon /></IconButton> + </CardFooter> + </Card> + + </GridItem>) + } + </GridContainer> + </div> + ); + } } diff --git a/jams-react-client/src/views/UserProfile/EditCreateUserProfile.js b/jams-react-client/src/views/UserProfile/EditCreateUserProfile.js index 728cf99602496a8d48149d628b9c41f0db1c47f2..2f6cfec94ae472fb21df8c9b9ba95fbd993e640c 100644 --- a/jams-react-client/src/views/UserProfile/EditCreateUserProfile.js +++ b/jams-react-client/src/views/UserProfile/EditCreateUserProfile.js @@ -120,36 +120,7 @@ const styles = (theme)=> ( { maxHeight: '60vh', minWidth: '80vh', maxWidth: '80vh' - }, - // controls: { - // padding: 16, - // display: 'flex', - // flexDirection: 'column', - // alignItems: 'stretch', - // [theme.breakpoints.up('sm')]: { - // flexDirection: 'row', - // alignItems: 'center', - // }, - // }, - // sliderContainer: { - // display: 'flex', - // flex: '1', - // alignItems: 'center', - // }, - // sliderLabel: { - // [theme.breakpoints.down('xs')]: { - // minWidth: 65, - // }, - // }, - // slider: { - // padding: '22px 0px', - // marginLeft: 16, - // [theme.breakpoints.up('sm')]: { - // flexDirection: 'row', - // alignItems: 'center', - // margin: '0 16px', - // }, - // }, + } }); const useStyles = makeStyles(styles);