From 546b11233adbcd5d4e920272d28abd01155758f9 Mon Sep 17 00:00:00 2001 From: Larbi Gharib <larbi.gharib@savoirfairelinux.com> Date: Tue, 29 Dec 2020 20:48:18 -0500 Subject: [PATCH] Manage moderator in blueprints Change-Id: Ie88b1eb6397a5ddf840ca8b06ac91af015ea2581 --- .../executionHistory/executionHistory.lock | Bin 0 -> 17 bytes .gradle/6.7/fileChanges/last-build.bin | Bin 0 -> 1 bytes .gradle/6.7/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .gradle/6.7/gc.properties | 0 .../buildOutputCleanup.lock | Bin 0 -> 17 bytes .gradle/buildOutputCleanup/cache.properties | 2 + .gradle/checksums/checksums.lock | Bin 0 -> 17 bytes .gradle/configuration-cache/gc.properties | 0 .gradle/vcs-1/gc.properties | 0 .../public/locales/en/translation.json | 5 +- .../public/locales/fr/translation.json | 5 +- .../src/components/Drawer/Drawer.js | 4 +- .../Blueprint/EditBlueprintPermissions.js | 164 +++++++++++++++++- .../src/views/Blueprints/Blueprints.js | 1 + .../src/views/Contacts/Contacts.js | 19 +- .../src/views/Groups/EditGroup.js | 1 + .../views/UserProfile/DisplayUserProfile.js | 1 + .../api/auth/device/DeviceServlet.java | 4 + 18 files changed, 192 insertions(+), 14 deletions(-) create mode 100644 .gradle/6.7/executionHistory/executionHistory.lock create mode 100644 .gradle/6.7/fileChanges/last-build.bin create mode 100644 .gradle/6.7/fileHashes/fileHashes.lock create mode 100644 .gradle/6.7/gc.properties create mode 100644 .gradle/buildOutputCleanup/buildOutputCleanup.lock create mode 100644 .gradle/buildOutputCleanup/cache.properties create mode 100644 .gradle/checksums/checksums.lock create mode 100644 .gradle/configuration-cache/gc.properties create mode 100644 .gradle/vcs-1/gc.properties diff --git a/.gradle/6.7/executionHistory/executionHistory.lock b/.gradle/6.7/executionHistory/executionHistory.lock new file mode 100644 index 0000000000000000000000000000000000000000..deb5f5b5e3140eb437a060755c82e6ad0017a534 GIT binary patch literal 17 TcmZRUHnrWj!F!cI0~7!NCPo7` literal 0 HcmV?d00001 diff --git a/.gradle/6.7/fileChanges/last-build.bin b/.gradle/6.7/fileChanges/last-build.bin new file mode 100644 index 0000000000000000000000000000000000000000..f76dd238ade08917e6712764a16a22005a50573d GIT binary patch literal 1 IcmZPo000310RR91 literal 0 HcmV?d00001 diff --git a/.gradle/6.7/fileHashes/fileHashes.lock b/.gradle/6.7/fileHashes/fileHashes.lock new file mode 100644 index 0000000000000000000000000000000000000000..350f6626b873da0a0ad7c4383f28ac25a0fc23bc GIT binary patch literal 17 TcmZQpy?Wuv9g%Ar7@z<EKf47e literal 0 HcmV?d00001 diff --git a/.gradle/6.7/gc.properties b/.gradle/6.7/gc.properties new file mode 100644 index 00000000..e69de29b diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000000000000000000000000000000000000..068d78e5c3ce8ab592dcda8633797dfadbd863eb GIT binary patch literal 17 TcmZQJE%wrW?S5T}0Rk8SC^iF4 literal 0 HcmV?d00001 diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 00000000..a92031e9 --- /dev/null +++ b/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Wed Feb 03 17:11:57 WEST 2021 +gradle.version=6.7 diff --git a/.gradle/checksums/checksums.lock b/.gradle/checksums/checksums.lock new file mode 100644 index 0000000000000000000000000000000000000000..61faee961424c0c9ae3f8863bfc4014ab6a2db96 GIT binary patch literal 17 TcmZRM_?qM+Q?zI{0~7!NFH{4{ literal 0 HcmV?d00001 diff --git a/.gradle/configuration-cache/gc.properties b/.gradle/configuration-cache/gc.properties new file mode 100644 index 00000000..e69de29b diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties new file mode 100644 index 00000000..e69de29b diff --git a/jams-react-client/public/locales/en/translation.json b/jams-react-client/public/locales/en/translation.json index 0ca38add..8f30c98f 100644 --- a/jams-react-client/public/locales/en/translation.json +++ b/jams-react-client/public/locales/en/translation.json @@ -236,5 +236,8 @@ "add_user_to_a_group": "Add user to a group", "remove_from_group": "Remove from group", "myprofile": "My profile", - "select_blueprint": "Select a blueprint" + "select_blueprint": "Select a blueprint", + "add_moderator_to_blueprint": "Add moderator to blueprint ...", + "add_moderator_to": "Add moderator to", + "remove_moderator": "Remove moderator" } diff --git a/jams-react-client/public/locales/fr/translation.json b/jams-react-client/public/locales/fr/translation.json index 0d32af77..e1a21ea1 100644 --- a/jams-react-client/public/locales/fr/translation.json +++ b/jams-react-client/public/locales/fr/translation.json @@ -236,5 +236,8 @@ "add_user_to_a_group": "Ajouter l'utilisateur à un groupe", "remove_from_group": "Retirer du groupe", "myprofile": "Mon profil", - "select_blueprint": "Select a blueprint" + "select_blueprint": "Select a blueprint", + "add_moderator_to_blueprint": "Add moderator to blueprint ...", + "add_moderator_to": "Add moderator to", + "remove_moderator": "Remove moderator" } diff --git a/jams-react-client/src/components/Drawer/Drawer.js b/jams-react-client/src/components/Drawer/Drawer.js index 6cb43848..ea35a6dc 100644 --- a/jams-react-client/src/components/Drawer/Drawer.js +++ b/jams-react-client/src/components/Drawer/Drawer.js @@ -44,7 +44,7 @@ export default function TemporaryDrawer(props) { const listUsers = () => ( <List> - {props.type === "user" ? ( props.targets && props.targets.map((target) => ( + {props.type === "user" ? ( props.targets && props.targets.filter((target) => !props.existingTargets.some(t => target.username == t.username)).map((target) => ( <ListItem button key={target.username} @@ -72,7 +72,7 @@ export default function TemporaryDrawer(props) { } /> </ListItem> - ))) : ( props.targets && props.targets.map((target) => ( + ))) : ( props.targets && props.targets.filter((target) => !props.existingTargets.some(t => target.name == t.name)).map((target) => ( <ListItem button key={target.name} diff --git a/jams-react-client/src/views/Blueprint/EditBlueprintPermissions.js b/jams-react-client/src/views/Blueprint/EditBlueprintPermissions.js index 8e25bee7..e7e369a2 100644 --- a/jams-react-client/src/views/Blueprint/EditBlueprintPermissions.js +++ b/jams-react-client/src/views/Blueprint/EditBlueprintPermissions.js @@ -1,7 +1,10 @@ import React from "react"; +import { Link, useHistory } from "react-router-dom"; // @material-ui/core components import { makeStyles } from "@material-ui/core/styles"; import Checkbox from "@material-ui/core/Checkbox"; +import classnames from "classnames"; + // core components import Grid from "@material-ui/core/Grid"; import GridItem from "components/Grid/GridItem.js"; @@ -12,6 +15,17 @@ import CardIcon from "components/Card/CardIcon.js"; import CardBody from "components/Card/CardBody.js"; import FormGroup from "@material-ui/core/FormGroup"; import FormControlLabel from "@material-ui/core/FormControlLabel"; +import Button from "components/CustomButtons/Button.js"; + +import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline'; + +import Avatar from "@material-ui/core/Avatar"; + +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 Snackbar from "@material-ui/core/Snackbar"; @@ -19,9 +33,18 @@ import { hexToRgb, blackColor } from "assets/jss/material-dashboard-react.js"; import PriorityHighOutlinedIcon from "@material-ui/icons/PriorityHighOutlined"; +import noProfilePicture from "assets/img/faces/no-profile-picture.png"; + import axios from "axios"; import configApiCall from "../../api"; -import { api_path_blueprints } from "../../globalUrls"; +import { + api_path_blueprints, + api_path_get_user_directory_search, + api_path_get_ns_name_from_addr, + api_path_get_user_profile +} from "../../globalUrls"; + +import TemporaryDrawer from "components/Drawer/Drawer" import dashboardStyle from "assets/jss/material-dashboard-react/views/dashboardStyle.js"; @@ -29,6 +52,8 @@ import MuiAlert from "@material-ui/lab/Alert"; import i18next from "i18next"; +import auth from "auth.js"; + const styles = (theme) => ({ ...dashboardStyle, root: { @@ -92,12 +117,20 @@ function Alert(props) { export default function EditBlueprintPermissions(props) { const classes = useStyles(); + const history = useHistory(); + + const tableCellClasses = classnames(classes.tableCell); + + const [openDrawer, setOpenDrawer] = React.useState(false); + const [users, setUsers] = React.useState([]); + const [blueprintModerators, setBlueprintModerators] = React.useState([]); const [videoEnabled, setVideoEnabled] = React.useState(true); const [publicInCalls, setPublicInCalls] = React.useState(true); const [autoAnswer, setAutoAnswer] = React.useState(false); const [peerDiscovery, setPeerDiscovery] = React.useState(true); const [rendezVous, setRendezVous] = React.useState(false); + const [defaultModerators, setDefaultModerators] = React.useState(""); const [upnpEnabled, setUpnpEnabled] = React.useState(true); const [turnEnabled, setTurnEnabled] = React.useState(true); @@ -112,7 +145,39 @@ export default function EditBlueprintPermissions(props) { const [message, setMessage] = React.useState(false); const [severity, setSeverity] = React.useState("success"); + const searchUsers = (value) => { + axios( + configApiCall( + api_path_get_user_directory_search, + "GET", + { queryString: value ? value : "*", page: "1" }, + null + ) + ) + .then((response) => { + let profiles = []; + const profilesResults = response.data.profiles; + profilesResults.forEach((profile) =>{ + let existingUser = false; + users.forEach((user)=>{ + if(profile.username === user.username) existingUser = true; + }) + if(!existingUser) profiles.push(profile); + }) + setUsers(profiles); + }) + .catch((error) => { + console.log(error); + setUsers([]); + if (error.response.status === 401) { + auth.authenticated = false; + history.push("/"); + } + }); + }; + React.useEffect(() => { + searchUsers(); axios( configApiCall( api_path_blueprints + "?name=" + props.blueprintName, @@ -128,6 +193,7 @@ export default function EditBlueprintPermissions(props) { setAutoAnswer(policyData["autoAnswer"]); setPeerDiscovery(policyData["peerDiscovery"]); setRendezVous(policyData["rendezVous"]); + setDefaultModerators(policyData["defaultModerators"]); setUpnpEnabled(policyData["upnpEnabled"]); setTurnEnabled(policyData["turnEnabled"]); @@ -137,6 +203,23 @@ export default function EditBlueprintPermissions(props) { setProxyEnabled(policyData["proxyEnabled"]); setProxyServer(policyData["proxyServer"]); setDhtProxyListUrl(policyData["dhtProxyListUrl"]); + + policyData["defaultModerators"].split("/").forEach((id) => { + if(id !== "undefined" && id !== ""){ + axios(configApiCall(api_path_get_ns_name_from_addr + id), null, null).then((usernameResponse) => { + let username = usernameResponse.data.name; + axios(configApiCall(api_path_get_user_profile + username), null, null).then((userProfileResponse) => { + let userProfiles = blueprintModerators; + userProfileResponse.data["id"] = id; + userProfiles.push(userProfileResponse.data) + setBlueprintModerators(userProfiles); + // This state update is added to refresh the list of moderators + setOpenDrawer(true); + setOpenDrawer(false); + }); + }); + } + }) }) .catch((error) => { console.log( @@ -157,6 +240,7 @@ export default function EditBlueprintPermissions(props) { accountDiscovery: peerDiscovery, accountPublish: peerDiscovery, rendezVous: rendezVous, + defaultModerators: defaultModerators, upnpEnabled: upnpEnabled, turnEnabled: turnEnabled, turnServer: turnServer, @@ -214,6 +298,28 @@ export default function EditBlueprintPermissions(props) { setOpen(false); }; + const addModeratorToBlueprint = (user) => { + if(defaultModerators.includes(user.id)){ + alert(`${user.username} is already a moderator of ${props.blueprintName}`); + } + else { + handleUpdatePermissions("defaultModerators", defaultModerators + user.id + "/"); + setDefaultModerators(defaultModerators + user.id + "/"); + let newBlueprintModerators = blueprintModerators; + newBlueprintModerators.push(user); + setBlueprintModerators(newBlueprintModerators); + } + } + + const removeModeratorFromBlueprint = (user) => { + let newDefaultModerators = defaultModerators.replace( user.id + "/", "") + handleUpdatePermissions("defaultModerators", newDefaultModerators); + setDefaultModerators(newDefaultModerators); + let newBlueprintModerators = blueprintModerators; + newBlueprintModerators.splice(newBlueprintModerators.indexOf(user), 1); + setBlueprintModerators(newBlueprintModerators); + } + return ( <div> <Snackbar @@ -227,6 +333,17 @@ export default function EditBlueprintPermissions(props) { {message} </Alert> </Snackbar> + <TemporaryDrawer + openDrawer={openDrawer} + setOpenDrawer={setOpenDrawer} + direction="right" + placeholder={i18next.t("add_moderator_to_blueprint", "Add moderator to blueprint ...")} + searchTargets={searchUsers} + targets={users} + existingTargets={blueprintModerators} + addElementToTarget={addModeratorToBlueprint} + type="user" + /> <GridContainer> <GridItem xs={12} sm={12} md={6}> <Card profile> @@ -342,6 +459,51 @@ export default function EditBlueprintPermissions(props) { </CardBody> </Card> </GridItem> + <GridItem xs={12} sm={12} md={12}> + <Button color="primary" onClick={() => {setOpenDrawer(true)}}><AddCircleOutlineIcon /> {i18next.t("add_moderator_to", "Add moderator to")} {props.blueprintName}</Button> + <Table className={classes.table}> + <TableHead> + <TableRow> + <TableCell align="left"></TableCell> + <TableCell align="left">{i18next.t("username", "Username")}</TableCell> + <TableCell align="left">{i18next.t("first_name", "First name")}</TableCell> + <TableCell align="left">{i18next.t("last_name", "Last name")}</TableCell> + <TableCell align="right">{i18next.t("action", "Action")}</TableCell> + </TableRow> + </TableHead> + <TableBody> + {blueprintModerators.map(user => + <TableRow key={user.username} className={classes.tableRow}> + <TableCell className={tableCellClasses}> + <Link to={`/user/${user.username}`}> + <Avatar + style={{ marginRight: "10px" }} + alt={user.username} + src={ + user.profilePicture + ? "data:image/png;base64, " + user.profilePicture + : noProfilePicture + } + /> + </Link> + </TableCell> + <TableCell className={tableCellClasses}> + <Link to={`/user/${user.username}`}>{user.username}</Link> + </TableCell> + <TableCell className={tableCellClasses}> + <Link to={`/user/${user.username}`}>{user.firstName}</Link> + </TableCell> + <TableCell className={tableCellClasses}> + <Link to={`/user/${user.username}`}>{user.lastName}</Link> + </TableCell> + <TableCell align="right" className={classes.tableActions}> + <Button color="primary" onClick={() => removeModeratorFromBlueprint(user)}>{i18next.t("remove_moderator", "Remove moderator")}</Button> + </TableCell> + </TableRow> + )} + </TableBody> + </Table> + </GridItem> </GridContainer> </div> ); diff --git a/jams-react-client/src/views/Blueprints/Blueprints.js b/jams-react-client/src/views/Blueprints/Blueprints.js index 6fd57cd5..9dc6c21e 100644 --- a/jams-react-client/src/views/Blueprints/Blueprints.js +++ b/jams-react-client/src/views/Blueprints/Blueprints.js @@ -188,6 +188,7 @@ export default function Blueprints() { accountDiscovery: true, accountPublish: true, rendezVous: false, + defaultModerators: "", upnpEnabled: true, }; diff --git a/jams-react-client/src/views/Contacts/Contacts.js b/jams-react-client/src/views/Contacts/Contacts.js index d101d23f..6464f091 100644 --- a/jams-react-client/src/views/Contacts/Contacts.js +++ b/jams-react-client/src/views/Contacts/Contacts.js @@ -148,10 +148,10 @@ export default function Users(props) { null ) ).then((response) => { - contact.name = response.data.name; + contact.username = response.data.name; axios( configApiCall( - api_path_get_user_profile + contact.name, + api_path_get_user_profile + contact.username, "GET", null, null @@ -191,10 +191,10 @@ export default function Users(props) { null ) ).then((response) => { - contact.name = response.data.name; + contact.username = response.data.name; axios( configApiCall( - api_path_get_user_profile + contact.name, + api_path_get_user_profile + contact.username, "GET", null, null @@ -336,6 +336,7 @@ export default function Users(props) { placeholder={i18next.t("add_contact", "Add contact…")} searchTargets={searchContacts} targets={users} + existingTargets={contacts} addElementToTarget={addContactToUser} targetName={props.username} type="user" @@ -418,8 +419,8 @@ export default function Users(props) { key={contact.uri} style={{ display: contact.display }} > - {contact.name && <Card profile> - <a href={`/user/${contact.name}`}> + {contact.username && <Card profile> + <a href={`/user/${contact.username}`}> <CardBody profile> <CardAvatar profile> <img @@ -436,13 +437,13 @@ export default function Users(props) { </h4> <ul> <li> - {contact.name && <img + {contact.username && <img src={jami} width="20" alt="Jami" style={{ marginRight: "10px" }} />} - {contact.name && ` ${contact.name}`} + {contact.username && ` ${contact.username}`} </li> <li> {contact.organization && <BusinessOutlinedIcon @@ -458,7 +459,7 @@ export default function Users(props) { <IconButton color="secondary" onClick={() => { - handleRemoveContact(contact.uri, contact.name); + handleRemoveContact(contact.uri, contact.username); }} > <DeleteOutlineIcon /> diff --git a/jams-react-client/src/views/Groups/EditGroup.js b/jams-react-client/src/views/Groups/EditGroup.js index 4c0ddf6c..5953ae3a 100644 --- a/jams-react-client/src/views/Groups/EditGroup.js +++ b/jams-react-client/src/views/Groups/EditGroup.js @@ -352,6 +352,7 @@ export default function EditGroup(props) { placeholder={i18next.t("add_user_to_group", "Add user to group ...")} searchTargets={searchUsers} targets={users} + existingTargets={groupMembers} addElementToTarget={addUserInGroup} targetName={name} type="user" diff --git a/jams-react-client/src/views/UserProfile/DisplayUserProfile.js b/jams-react-client/src/views/UserProfile/DisplayUserProfile.js index d5bcfa31..282ff263 100644 --- a/jams-react-client/src/views/UserProfile/DisplayUserProfile.js +++ b/jams-react-client/src/views/UserProfile/DisplayUserProfile.js @@ -535,6 +535,7 @@ export default function DisplayUserProfile(props) { placeholder={i18next.t("add_user_to_group", "Add user to group ...")} searchTargets={searchGroups} targets={groups} + existingTargets={groupMemberships} addElementToTarget={addUserToGroup} targetName={props.username} type="group" diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/device/DeviceServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/device/DeviceServlet.java index e7edda95..9df155dd 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/device/DeviceServlet.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/device/DeviceServlet.java @@ -193,6 +193,10 @@ public class DeviceServlet extends HttpServlet { obj.add("Account.displayName", obj.get("displayName")); obj.remove("displayName"); } + if (obj.get("defaultModerators") != null) { + obj.add("Account.defaultModerators", obj.get("defaultModerators")); + obj.remove("defaultModerators"); + } resp.getOutputStream().write((obj.toString()).getBytes()); } -- GitLab