diff --git a/jams-react-client/src/assets/jss/material-dashboard-react/components/cardAvatarStyle.js b/jams-react-client/src/assets/jss/material-dashboard-react/components/cardAvatarStyle.js index da79a3f9c08a9c193ecc51f7b62c4af8ff7d01b1..5bf38201f76cf7bf0465c42a7dd68bac92a09aba 100644 --- a/jams-react-client/src/assets/jss/material-dashboard-react/components/cardAvatarStyle.js +++ b/jams-react-client/src/assets/jss/material-dashboard-react/components/cardAvatarStyle.js @@ -4,8 +4,8 @@ const cardAvatarStyle = { cardAvatar: { "&$cardAvatarProfile img": { width: "100%", - height: "auto" - } + height: "auto", + }, }, cardAvatarProfile: { maxWidth: "80px", @@ -23,8 +23,8 @@ const cardAvatarStyle = { hexToRgb(blackColor) + ", 0.2)", "&$cardAvatarPlain": { - marginTop: "0" - } + marginTop: "0", + }, }, cardAvatarDisplayProfile: { maxWidth: "200px", @@ -42,8 +42,8 @@ const cardAvatarStyle = { hexToRgb(blackColor) + ", 0.2)", "&$cardAvatarPlain": { - marginTop: "0" - } + marginTop: "0", + }, }, cardAvatarEditProfile: { maxWidth: "200px", @@ -61,10 +61,10 @@ const cardAvatarStyle = { hexToRgb(blackColor) + ", 0.2)", "&$cardAvatarPlain": { - marginTop: "0" - } + marginTop: "0", + }, }, - cardAvatarPlain: {} + cardAvatarPlain: {}, }; export default cardAvatarStyle; diff --git a/jams-react-client/src/views/Settings/Settings.js b/jams-react-client/src/views/Settings/Settings.js index 6174f4527d137621dce7e1a58ac6b0aeaad33a1c..b6166739d297b7b3af4050357f742f7adc0842b6 100644 --- a/jams-react-client/src/views/Settings/Settings.js +++ b/jams-react-client/src/views/Settings/Settings.js @@ -5,107 +5,130 @@ import { makeStyles } from "@material-ui/core/styles"; import IdentityManagement from "components/IdentityManagement/IdentityManagement.js"; import ServerParameters from "components/ServerParameters/ServerParameters.js"; import UpdatePassword from "./UpdatePassword"; -import Subscription from "./Subscription" +import Subscription from "./Subscription"; +import PropTypes from "prop-types"; +import AppBar from "@material-ui/core/AppBar"; +import Tabs from "@material-ui/core/Tabs"; +import Tab from "@material-ui/core/Tab"; +import Typography from "@material-ui/core/Typography"; +import Box from "@material-ui/core/Box"; -import PropTypes from 'prop-types'; -import AppBar from '@material-ui/core/AppBar'; -import Tabs from '@material-ui/core/Tabs'; -import Tab from '@material-ui/core/Tab'; -import Typography from '@material-ui/core/Typography'; -import Box from '@material-ui/core/Box'; - -import { - infoColor -} from "assets/jss/material-dashboard-react.js"; +import { infoColor } from "assets/jss/material-dashboard-react.js"; import MuiAlert from "@material-ui/lab/Alert"; +import auth from "auth.js"; function TabPanel(props) { - const { children, value, index, ...other } = props; + const { children, value, index, ...other } = props; - return ( - <div - role="tabpanel" - hidden={value !== index} - id={`simple-tabpanel-${index}`} - aria-labelledby={`simple-tab-${index}`} - {...other} - > - {value === index && ( - <Box p={3}> - <Typography>{children}</Typography> - </Box> - )} - </div> - ); + return ( + <div + role="tabpanel" + hidden={value !== index} + id={`simple-tabpanel-${index}`} + aria-labelledby={`simple-tab-${index}`} + {...other} + > + {value === index && ( + <Box p={3}> + <Typography>{children}</Typography> + </Box> + )} + </div> + ); } TabPanel.propTypes = { - children: PropTypes.node, - index: PropTypes.any.isRequired, - value: PropTypes.any.isRequired, + children: PropTypes.node, + index: PropTypes.any.isRequired, + value: PropTypes.any.isRequired, }; function a11yProps(index) { - return { - id: `simple-tab-${index}`, - 'aria-controls': `simple-tabpanel-${index}`, - }; + return { + id: `simple-tab-${index}`, + "aria-controls": `simple-tabpanel-${index}`, + }; } function Alert(props) { - return <MuiAlert elevation={6} variant="filled" {...props} />; + return <MuiAlert elevation={6} variant="filled" {...props} />; } - const styles = { - 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" - } + 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", + }, }; const useStyles = makeStyles(styles); export default function Settings(props) { - const classes = useStyles(); + const classes = useStyles(); - const [value, setValue] = React.useState(0); - const [error, setError] = React.useState(false); - const [severity, setSeverity] = React.useState("error"); - const [errorMessage, setErrorMessage] = React.useState("Test"); + const [value, setValue] = React.useState(0); + const [error, setError] = React.useState(false); + const [severity, setSeverity] = React.useState("error"); + const [errorMessage, setErrorMessage] = React.useState("Test"); - const handleChange = (event, newValue) => { - setValue(newValue); - }; + const handleChange = (event, newValue) => { + setValue(newValue); + }; + if (!auth.hasAdminScope()) { + return ( + <div> + <h4> + You are not allowed to access this section. Please contact your + administrator to get administrator privileges. + </h4> + </div> + ); + } else { return ( - <div> - <AppBar position="static" style={{ background: infoColor[0] }}> - <Tabs value={value} onChange={handleChange} aria-label="simple tabs example"> - <Tab label="Admin Password" {...a11yProps(0)} /> - <Tab label="Subscription" {...a11yProps(1)} /> - </Tabs> - </AppBar> - <TabPanel value={value} index={0}> - <UpdatePassword username="admin" setError={setError} setErrorMessage={setErrorMessage} setSeverity={setSeverity}/> - </TabPanel> - <TabPanel value={value} index={1}> - <Subscription setError={setError} setErrorMessage={setErrorMessage} setSeverity={setSeverity}/> - </TabPanel> - {error && errorMessage && <Alert severity={severity}>{errorMessage}</Alert>} - </div> + <div> + <AppBar position="static" style={{ background: infoColor[0] }}> + <Tabs + value={value} + onChange={handleChange} + aria-label="simple tabs example" + > + <Tab label="Admin Password" {...a11yProps(0)} /> + <Tab label="Subscription" {...a11yProps(1)} /> + </Tabs> + </AppBar> + <TabPanel value={value} index={0}> + <UpdatePassword + username="admin" + setError={setError} + setErrorMessage={setErrorMessage} + setSeverity={setSeverity} + /> + </TabPanel> + <TabPanel value={value} index={1}> + <Subscription + setError={setError} + setErrorMessage={setErrorMessage} + setSeverity={setSeverity} + /> + </TabPanel> + {error && errorMessage && ( + <Alert severity={severity}>{errorMessage}</Alert> + )} + </div> ); + } } diff --git a/jams-react-client/src/views/UserProfile/DisplayUserProfile.js b/jams-react-client/src/views/UserProfile/DisplayUserProfile.js index 0caa4806741b310b984ef1001503d67b89c1bd14..dc0907bbaa3cf5b4e53d4c7ec41ed4d8a64ee815 100644 --- a/jams-react-client/src/views/UserProfile/DisplayUserProfile.js +++ b/jams-react-client/src/views/UserProfile/DisplayUserProfile.js @@ -1,4 +1,5 @@ import React, { useEffect } from "react"; +import { useHistory } from "react-router-dom"; // @material-ui/core components import { makeStyles } from "@material-ui/core/styles"; // core components @@ -18,6 +19,7 @@ import DialogContentText from "@material-ui/core/DialogContentText"; import DialogTitle from "@material-ui/core/DialogTitle"; import EditIcon from "@material-ui/icons/Edit"; import DeleteIcon from "@material-ui/icons/Delete"; +import GroupIcon from "@material-ui/icons/Group"; import Grid from "@material-ui/core/Grid"; import BusinessCenterOutlinedIcon from "@material-ui/icons/BusinessCenterOutlined"; @@ -47,6 +49,8 @@ import { api_path_get_auth_user, api_path_get_user_profile, api_path_delete_admin_user_revoke, + api_path_get_list_group, + api_path_put_update_group, } from "globalUrls"; import dashboardStyle from "assets/jss/material-dashboard-react/views/dashboardStyle.js"; @@ -118,24 +122,48 @@ const styles = (theme) => ({ flexDirection: "column", }, }, + userProfileHeader: { + display: "flex", + justifyContent: "space-between", + [theme.breakpoints.down("md")]: { + display: "flex", + flexDirection: "column", + textAlign: "center", + }, + }, + groups: { + display: "flex", + justifyContent: "center", + flexWrap: "wrap", + "& > *": { + margin: theme.spacing(0.5), + }, + }, + cardAvatarMobile: { + [theme.breakpoints.down("md")]: { + margin: "-50px 50px 0", + }, + }, }); const useStyles = makeStyles(styles); export default function DisplayUserProfile(props) { const classes = useStyles(); - + const history = useHistory(); const [user, setUser] = React.useState([]); const [revoked, setRevoked] = React.useState(false); const [open, setOpen] = React.useState(false); const [revokedUser, setRevokedUser] = React.useState(""); const [changePasswordOpen, setChangePasswordOpen] = React.useState(false); - const userData = { - username: props.username, - }; + const [groups, setGroups] = React.useState([]); + const [zeroGroup, setZeroGroup] = React.useState(false); useEffect(() => { + const userData = { + username: props.username, + }; auth.checkDirectoryType(() => { if (auth.hasAdminScope()) { axios(configApiCall(api_path_get_admin_user, "GET", userData, null)) @@ -144,37 +172,94 @@ export default function DisplayUserProfile(props) { response.data.replace(/\s+/g, " ").trim() ); setRevoked(result.revoked); + axios( + configApiCall( + api_path_get_user_profile + props.username, + "GET", + null, + null + ) + ) + .then((response) => { + setUser(response.data); + const groupMemberships = response.data.groupMemberships; + axios( + configApiCall( + api_path_get_list_group + "?groupName=*", + "GET", + null, + null + ) + ) + .then((response) => { + let allGroups = response.data; + if (allGroups.length === 0) setZeroGroup(true); + else { + setZeroGroup(false); + + allGroups.forEach((group) => { + group.actif = false; + }); + + allGroups.forEach((group) => { + groupMemberships.forEach((userGroup) => { + if (group.name === userGroup) { + group.actif = true; + } + }); + }); + } + setGroups(allGroups); + }) + .catch((error) => { + console.log(error); + if (error.response.status === 401) { + auth.authenticated = false; + history.push("/"); + } + }); + }) + .catch((error) => { + console.log(error); + }); }) .catch((error) => { console.log(error); }); } else { - axios(configApiCall(api_path_get_auth_user, "GET", userData, null)) + axios( + configApiCall( + api_path_get_auth_user + "?username=" + props.username, + "GET", + null, + null + ) + ) .then((response) => { const result = JSON.parse( response.data.replace(/\s+/g, " ").trim() ); setRevoked(result.revoked); + axios( + configApiCall( + api_path_get_user_profile + props.username, + "GET", + null, + null + ) + ) + .then((response) => { + setUser(response.data); + }) + .catch((error) => { + console.log(error); + }); }) .catch((error) => { console.log(error); }); } }); - axios( - configApiCall( - api_path_get_user_profile + props.username, - "GET", - null, - null - ) - ) - .then((response) => { - setUser(response.data); - }) - .catch((error) => { - console.log(error); - }); }, []); const getUserStatus = () => { @@ -200,7 +285,7 @@ export default function DisplayUserProfile(props) { } }; - function revokeUser() { + const revokeUser = () => { const data = { username: revokedUser, }; @@ -217,7 +302,7 @@ export default function DisplayUserProfile(props) { ); }); setOpen(false); - } + }; const handleClickOpen = (username) => { setRevokedUser(username); @@ -232,6 +317,46 @@ export default function DisplayUserProfile(props) { setChangePasswordOpen(false); }; + const updateUserGroup = (group) => { + const newGroups = groups; + newGroups.forEach((g) => { + if (g === group) { + if (group.actif) g.actif = false; + else g.actif = true; + } + }); + let url = ""; + if (group.blueprint == null) { + url = + api_path_put_update_group + + "?groupName=" + + group.name + + "&newName=" + + group.name + + "&blueprintName=&groupMembers=" + + [props.username]; + } else { + url = + api_path_put_update_group + + "?groupName=" + + group.name + + "&newName=" + + group.name + + "&blueprintName=" + + group.blueprint + + "&groupMembers=" + + [props.username]; + } + + axios(configApiCall(url, "PUT", null, null)) + .then(() => { + setGroups(newGroups); + }) + .catch((error) => { + console.log("Error updating group: " + error); + }); + }; + return ( <div> <Dialog @@ -270,7 +395,10 @@ export default function DisplayUserProfile(props) { <div className={classes.root}> <Grid container spacing={2}> <Grid item xs={12} sm={12} md={6}> - <CardAvatar displayProfile> + <CardAvatar + displayProfile + className={classes.cardAvatarMobile} + > <img src={ user.profilePicture @@ -283,12 +411,7 @@ export default function DisplayUserProfile(props) { </CardAvatar> </Grid> <Grid item xs={12} sm={12} md={6}> - <div - style={{ - display: "flex", - justifyContent: "space-between", - }} - > + <div className={classes.userProfileHeader}> <div> <h3 className={classes.cardTitle}> {user.username ? user.username : "no username"} @@ -309,113 +432,114 @@ export default function DisplayUserProfile(props) { </Grid> <Grid item xs={12} sm={12} md={6}> <List dense={false}> - <ListItem> - <ListItemAvatar> - <Avatar> - <PersonIcon /> - </Avatar> - </ListItemAvatar> - <ListItemText - primary={ - user.firstName ? user.firstName : "no firstname" - } - /> - </ListItem> - <ListItem> - <ListItemAvatar> - <Avatar> - <PhoneInTalkOutlinedIcon /> - </Avatar> - </ListItemAvatar> - <ListItemText - primary={ - user.phoneNumber - ? user.phoneNumber - : "no phone number" - } - /> - </ListItem> - <ListItem> - <ListItemAvatar> - <Avatar> - <AlternateEmailOutlinedIcon /> - </Avatar> - </ListItemAvatar> - <ListItemText - primary={user.email ? user.email : "no email"} - /> - </ListItem> - <ListItem> - <ListItemAvatar> - <Avatar> - <BusinessCenterOutlinedIcon /> - </Avatar> - </ListItemAvatar> - <ListItemText - primary={ - user.organization - ? user.organization - : "no organization" - } - /> - </ListItem> + {user.firstName && ( + <ListItem> + <ListItemAvatar> + <Avatar> + <PersonIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={user.firstName} /> + </ListItem> + )} + {user.phoneNumber && ( + <ListItem> + <ListItemAvatar> + <Avatar> + <PhoneInTalkOutlinedIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={user.phoneNumber} /> + </ListItem> + )} + {user.email && ( + <ListItem> + <ListItemAvatar> + <Avatar> + <AlternateEmailOutlinedIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={user.email} /> + </ListItem> + )} + {user.organization && ( + <ListItem> + <ListItemAvatar> + <Avatar> + <BusinessCenterOutlinedIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={user.organization} /> + </ListItem> + )} </List> </Grid> <Grid item xs={12} sm={12} md={6}> <List dense={false}> - <ListItem> - <ListItemAvatar> - <Avatar> - <PersonOutlinedIcon /> - </Avatar> - </ListItemAvatar> - <ListItemText - primary={ - user.lastName ? user.lastName : "no lastname" - } - /> - </ListItem> - <ListItem> - <ListItemAvatar> - <Avatar> - <PhoneForwardedOutlinedIcon /> - </Avatar> - </ListItemAvatar> - <ListItemText - primary={ - user.phoneNumberExtension - ? user.phoneNumberExtension - : "no Extension" - } - /> - </ListItem> - <ListItem> - <ListItemAvatar> - <Avatar> - <SmartphoneOutlinedIcon /> - </Avatar> - </ListItemAvatar> - <ListItemText - primary={ - user.mobileNumber - ? user.mobileNumber - : "no mobile number" - } - /> - </ListItem> - <ListItem> - <ListItemAvatar> - <Avatar> - <LocalPrintshopOutlinedIcon /> - </Avatar> - </ListItemAvatar> - <ListItemText - primary={ - user.faxNumber ? user.faxNumber : "no fax number" - } - /> - </ListItem> + {user.lastName && ( + <ListItem> + <ListItemAvatar> + <Avatar> + <PersonOutlinedIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={user.lastName} /> + </ListItem> + )} + {user.phoneNumberExtension && ( + <ListItem> + <ListItemAvatar> + <Avatar> + <PhoneForwardedOutlinedIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={user.phoneNumberExtension} /> + </ListItem> + )} + {user.mobileNumber && ( + <ListItem> + <ListItemAvatar> + <Avatar> + <SmartphoneOutlinedIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={user.mobileNumber} /> + </ListItem> + )} + {user.faxNumber && ( + <ListItem> + <ListItemAvatar> + <Avatar> + <LocalPrintshopOutlinedIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={user.faxNumber} /> + </ListItem> + )} + {auth.hasAdminScope() && !zeroGroup && ( + <ListItem> + <ListItemAvatar> + <Avatar> + <GroupIcon /> + </Avatar> + </ListItemAvatar> + <div className={classes.groups}> + {groups.map((group) => ( + <Chip + style={{ flex: 1 }} + label={group.name} + variant="default" + clickable + size="medium" + onClick={() => updateUserGroup(group)} + color={group.actif ? "primary" : "default"} + icon={group.actif ? <DoneIcon /> : ""} + /> + ))} + </div> + </ListItem> + )} </List> </Grid> </Grid> diff --git a/jams-react-client/src/views/UserProfile/EditCreateUserProfile.js b/jams-react-client/src/views/UserProfile/EditCreateUserProfile.js index dcf27ec0642e7a875953445ab8255953fdaa6885..99cd36341df438618676a745d4489d8dc3083d53 100644 --- a/jams-react-client/src/views/UserProfile/EditCreateUserProfile.js +++ b/jams-react-client/src/views/UserProfile/EditCreateUserProfile.js @@ -797,7 +797,7 @@ export default function EditCreateUserProfile(props) { placeholder="Phone number" startAdornment={ <InputAdornment position="start"> - <LocalPrintshopOutlinedIcon /> + <PhoneInTalkOutlinedIcon /> </InputAdornment> } /> diff --git a/jams-react-client/src/views/UserProfile/UserProfile.js b/jams-react-client/src/views/UserProfile/UserProfile.js index 3ae9d6f36acb1f64038e1eb2229b51ac61c3bb0a..2cbf7b6ee23c15b2df1b3f16f7e803dbbe57ce47 100755 --- a/jams-react-client/src/views/UserProfile/UserProfile.js +++ b/jams-react-client/src/views/UserProfile/UserProfile.js @@ -82,6 +82,9 @@ export default function UserProfile(props) { const handleChange = (event, newValue) => { setValue(newValue); }; + + React.useEffect(() => {}, []); + return ( <div> <AppBar position="static" style={{ background: infoColor[0] }}> diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/user/UserServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/user/UserServlet.java index c732284ec898ae7d302f053a2a2ba81157aff0b0..1fe53042ac53ff1733766445e0732d0faba12b45 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/user/UserServlet.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/user/UserServlet.java @@ -83,7 +83,7 @@ public class UserServlet extends HttpServlet { resp.setStatus(200); resp.getOutputStream().write(JsonStream.serialize(user).getBytes()); } - resp.sendError(404, "User was not found!"); + else resp.sendError(404, "User was not found!"); } //The user can update 3 fields: password,privatekey,publickey