1736 lines
74 KiB
React
1736 lines
74 KiB
React
import React, { useState, useEffect, useContext, memo } from "react";
|
||
import { toast } from 'react-toastify';
|
||
import { Context } from "../context/ContextApi.jsx";
|
||
import {
|
||
FormControl,
|
||
InputLabel,
|
||
OutlinedInput,
|
||
Checkbox,
|
||
Tooltip,
|
||
Typography,
|
||
Select,
|
||
MenuItem,
|
||
Divider,
|
||
TextField,
|
||
Button,
|
||
List,
|
||
ListItem,
|
||
ListItemText,
|
||
IconButton,
|
||
Dialog,
|
||
DialogTitle,
|
||
DialogActions,
|
||
DialogContent,
|
||
CircularProgress,
|
||
Skeleton,
|
||
Switch,
|
||
Box,
|
||
} from "@mui/material";
|
||
|
||
import {
|
||
Cached as CachedIcon,
|
||
Edit as EditIcon,
|
||
Style,
|
||
} from "@mui/icons-material";
|
||
import ModeEditOutlineOutlinedIcon from '@mui/icons-material/ModeEditOutlineOutlined';
|
||
import ContentCopyOutlinedIcon from '@mui/icons-material/ContentCopyOutlined';
|
||
|
||
import {getTheme} from "../theme.jsx";
|
||
const ITEM_HEIGHT = 48;
|
||
const ITEM_PADDING_TOP = 8;
|
||
const MenuProps = {
|
||
PaperProps: {
|
||
style: {
|
||
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
|
||
width: 500,
|
||
},
|
||
},
|
||
getContentAnchorEl: () => null,
|
||
};
|
||
|
||
const logsViewModal = false;
|
||
const userdata = "";
|
||
|
||
const UserManagmentTab = memo((props) => {
|
||
const { userdata, isCloud, globalUrl, selectedOrganization, handleEditOrg} = props;
|
||
const [modalOpen, setModalOpen] = React.useState(false);
|
||
const [loginInfo, setLoginInfo] = React.useState("");
|
||
const [modalUser, setModalUser] = React.useState({});
|
||
const [selectedUser, setSelectedUser] = React.useState({});
|
||
const [matchingOrganizations, setMatchingOrganizations] = React.useState([]);
|
||
const [selectedUserModalOpen, setSelectedUserModalOpen] = React.useState(false);
|
||
const [image2FA, setImage2FA] = React.useState("");
|
||
const [secret2FA, setSecret2FA] = React.useState("");
|
||
const [value2FA, setValue2FA] = React.useState("");
|
||
const [newUsername, setNewUsername] = React.useState("");
|
||
const [newPassword, setNewPassword] = React.useState("");
|
||
const [show2faSetup, setShow2faSetup] = useState(false);
|
||
const [showDeleteAccountTextbox, setShowDeleteAccountTextbox] = React.useState(false);
|
||
const [MFARequired, setMFARequired] = React.useState(selectedOrganization.mfa_required === undefined ? false : selectedOrganization.mfa_required);
|
||
const [deleteAccountText, setDeleteAccountText] = React.useState("");
|
||
const [users, setUsers] = React.useState([]);
|
||
const [showLoader, setShowLoader] = useState(true);
|
||
const [logsLoading, setLogsLoading] = React.useState(true);
|
||
const [logs, setLogs] = React.useState([]);
|
||
const [logsViewModal, setLogsViewModal] = React.useState(false);
|
||
const [ipSelected, setIpSelected] = React.useState("");
|
||
const [userLogViewing, setUserLogViewing] = React.useState({});
|
||
const { themeMode, supportEmail, brandColor } = useContext(Context);
|
||
const theme = getTheme(themeMode, brandColor);
|
||
|
||
|
||
useEffect(() => {
|
||
if (selectedOrganization?.mfa_required !== MFARequired) {
|
||
setMFARequired(selectedOrganization?.mfa_required);
|
||
}
|
||
}, [selectedOrganization]);
|
||
|
||
useEffect(() => { if(users?.length === 0){
|
||
getUsers();
|
||
} }, []);
|
||
|
||
const changeModalData = (field, value) => {
|
||
modalUser[field] = value;
|
||
};
|
||
|
||
const submitUser = (data) => {
|
||
console.log("INPUT: ", data);
|
||
setLoginInfo("");
|
||
|
||
// Just use this one?
|
||
var data = { username: data.Username, password: data.Password };
|
||
var baseurl = globalUrl;
|
||
const url = baseurl + "/api/v1/users/register";
|
||
|
||
fetch(url, {
|
||
method: "POST",
|
||
credentials: "include",
|
||
body: JSON.stringify(data),
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
})
|
||
.then((response) =>
|
||
response.json().then((responseJson) => {
|
||
if (responseJson["success"] === false) {
|
||
setLoginInfo("Error: " + responseJson.reason);
|
||
} else {
|
||
setLoginInfo("");
|
||
toast.success("User added successfully. They will show up in the list when they have accepted the invite.");
|
||
setModalOpen(false);
|
||
setTimeout(() => {
|
||
getUsers();
|
||
}, 1000);
|
||
}
|
||
})
|
||
)
|
||
.catch((error) => {
|
||
console.log("Error in userdata: ", error);
|
||
});
|
||
};
|
||
|
||
const setUser = (userId, field, value) => {
|
||
const data = { user_id: userId };
|
||
data[field] = value;
|
||
|
||
fetch(globalUrl + "/api/v1/users/updateuser", {
|
||
method: "PUT",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
Accept: "application/json",
|
||
},
|
||
body: JSON.stringify(data),
|
||
credentials: "include",
|
||
})
|
||
.then((response) => {
|
||
if (response.status !== 200) {
|
||
console.log("Status not 200 for WORKFLOW EXECUTION :O!");
|
||
} else {
|
||
getUsers();
|
||
}
|
||
|
||
return response.json();
|
||
})
|
||
.then((responseJson) => {
|
||
if (!responseJson.success && responseJson.reason !== undefined) {
|
||
toast("Failed setting user: " + responseJson.reason);
|
||
} else if (responseJson.success === false) {
|
||
toast("Failed to update user");
|
||
} else {
|
||
//toast("Set the user field " + field + " to " + value);
|
||
toast("Successfully updated user field " + field);
|
||
|
||
if (field !== "suborgs") {
|
||
setSelectedUserModalOpen(false);
|
||
}
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
console.log(error);
|
||
});
|
||
};
|
||
|
||
const inviteUser = (data) => {
|
||
//console.log("INPUT: ", data);
|
||
setLoginInfo("");
|
||
|
||
// Just use this one?
|
||
var data = {
|
||
username: data.Username,
|
||
type: "invite",
|
||
org_id: selectedOrganization.id,
|
||
};
|
||
var baseurl = globalUrl;
|
||
const url = baseurl + "/api/v1/users/register_org";
|
||
|
||
fetch(url, {
|
||
method: "POST",
|
||
credentials: "include",
|
||
body: JSON.stringify(data),
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
})
|
||
.then((response) =>
|
||
response.json().then((responseJson) => {
|
||
if (responseJson["success"] === false) {
|
||
setLoginInfo("Error: " + responseJson.reason);
|
||
toast("Failed to send email (2). Please try again and contact support if this persists.")
|
||
} else {
|
||
setLoginInfo("");
|
||
setModalOpen(false);
|
||
setTimeout(() => {
|
||
getUsers();
|
||
}, 1000);
|
||
|
||
toast("Invite sent! They will show up in the list when they have accepted the invite.")
|
||
}
|
||
})
|
||
)
|
||
.catch((error) => {
|
||
console.log("Error in userdata: ", error);
|
||
toast("Failed to send email. Please try again and contact support if this persists.")
|
||
});
|
||
};
|
||
const onPasswordChange = () => {
|
||
const data = { username: selectedUser.username, newpassword: newPassword };
|
||
const url = globalUrl + "/api/v1/users/passwordchange";
|
||
|
||
fetch(url, {
|
||
mode: "cors",
|
||
method: "POST",
|
||
body: JSON.stringify(data),
|
||
credentials: "include",
|
||
crossDomain: true,
|
||
withCredentials: true,
|
||
headers: {
|
||
"Content-Type": "application/json; charset=utf-8",
|
||
},
|
||
})
|
||
.then((response) =>
|
||
response.json().then((responseJson) => {
|
||
if (responseJson["success"] === false) {
|
||
if (responseJson.reason !== undefined) {
|
||
toast(responseJson.reason);
|
||
} else {
|
||
toast("Failed setting new password");
|
||
}
|
||
} else {
|
||
toast("Successfully updated password!");
|
||
setSelectedUserModalOpen(false);
|
||
}
|
||
}),
|
||
)
|
||
.catch((error) => {
|
||
toast("Err: " + error.toString());
|
||
});
|
||
};
|
||
|
||
const handleOrgEditChange = (event) => {
|
||
if (userdata.id === selectedUser.id) {
|
||
toast("Can't remove orgs from yourself");
|
||
return;
|
||
}
|
||
|
||
if (event.target.value.includes("ALL")) {
|
||
toast.info("Adding to available all sub-organizations. This may take a minute.")
|
||
event.target.value = selectedOrganization.child_orgs.map((org) => org.id)
|
||
} else if (event.target.value.includes("None")) {
|
||
toast.info("Removing from all sub-organizations. This may take a minute")
|
||
event.target.value = []
|
||
}
|
||
|
||
console.log("event: ", event.target.value);
|
||
setMatchingOrganizations(event.target.value);
|
||
// Workaround for empty orgs
|
||
if (event.target.value.length === 0) {
|
||
event.target.value.push("REMOVE");
|
||
}
|
||
|
||
setUser(selectedUser.id, "suborgs", event.target.value);
|
||
//setUser(selectedUser.id, "suborgs", matchingOrganizations)
|
||
};
|
||
|
||
const userOrgEdit =
|
||
selectedUser.id !== undefined &&
|
||
selectedUser?.orgs !== undefined &&
|
||
selectedUser?.orgs !== null &&
|
||
selectedOrganization?.child_orgs !== undefined &&
|
||
selectedOrganization?.child_orgs !== null &&
|
||
selectedOrganization?.child_orgs?.length > 0 ? (
|
||
<FormControl fullWidth sx={{ m: 1 }}>
|
||
<InputLabel id="demo-multiple-checkbox-label" style={{ padding: 5 }}>
|
||
Accessible Sub-Organizations (
|
||
{selectedUser?.orgs ? selectedUser?.orgs?.length - 1 : 0})
|
||
</InputLabel>
|
||
<Select
|
||
fullWidth
|
||
style={{ width: "100%" }}
|
||
disabled={selectedUser?.id === userdata?.id}
|
||
labelId="demo-multiple-checkbox-label"
|
||
id="demo-multiple-checkbox"
|
||
multiple
|
||
value={matchingOrganizations}
|
||
onChange={handleOrgEditChange}
|
||
input={<OutlinedInput label="Tag" />}
|
||
renderValue={(selected) => {
|
||
return selected.join(", ");
|
||
}}
|
||
MenuProps={MenuProps}
|
||
>
|
||
<MenuItem key={-2} value={"None"}>
|
||
<Checkbox checked={false} />
|
||
<ListItemText primary={"None"} />
|
||
</MenuItem>
|
||
<MenuItem key={-1} value={"ALL"}>
|
||
<Checkbox checked={false} />
|
||
<ListItemText primary={"ALL"} />
|
||
</MenuItem>
|
||
{selectedOrganization.child_orgs.map((org, index) => (
|
||
<MenuItem key={index} value={org.id}>
|
||
<Checkbox checked={matchingOrganizations.indexOf(org.id) > -1} />
|
||
<ListItemText primary={org.name} />
|
||
</MenuItem>
|
||
))}
|
||
</Select>
|
||
</FormControl>
|
||
) : null;
|
||
|
||
const getUsers = () => {
|
||
fetch(globalUrl + "/api/v1/getusers", {
|
||
method: "GET",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
Accept: "application/json",
|
||
},
|
||
credentials: "include",
|
||
})
|
||
.then((response) => {
|
||
if (response.status !== 200) {
|
||
// Ahh, this happens because they're not admin
|
||
// window.location.pathname = "/workflows"
|
||
return;
|
||
}
|
||
|
||
return response.json();
|
||
})
|
||
.then((responseJson) => {
|
||
setUsers(responseJson);
|
||
setShowLoader(false)
|
||
})
|
||
.catch((error) => {
|
||
toast(error.toString());
|
||
});
|
||
};
|
||
|
||
const deleteUser = (data) => {
|
||
// Just use this one?
|
||
const userId = data.id;
|
||
|
||
const url = globalUrl + "/api/v1/users/" + userId;
|
||
fetch(url, {
|
||
method: "DELETE",
|
||
credentials: "include",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
})
|
||
.then((response) => {
|
||
if (response.status === 200) {
|
||
getUsers();
|
||
}
|
||
|
||
return response.json();
|
||
})
|
||
.then((responseJson) => {
|
||
if (!responseJson.success && responseJson.reason !== undefined) {
|
||
toast("Failed to deactivate user: " + responseJson.reason);
|
||
} else if (responseJson.success === false) {
|
||
toast(
|
||
`Failed to deactivate user. Please contact ${supportEmail} if this persists.`,
|
||
);
|
||
} else {
|
||
toast("Changed activation for user " + data.id);
|
||
}
|
||
})
|
||
|
||
.catch((error) => {
|
||
console.log("Error in userdata: ", error);
|
||
});
|
||
};
|
||
|
||
const handleDeleteAccount = (userID) => {
|
||
if (userID === undefined || userID === null || userID === "") {
|
||
return;
|
||
}
|
||
|
||
const url = `${globalUrl}/api/v1/users/${userID}/remove`;
|
||
fetch(url, {
|
||
mode: "cors",
|
||
method: "DELETE",
|
||
credentials: "include",
|
||
crossDomain: true,
|
||
withCredentials: true,
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (data.success) {
|
||
toast.success(
|
||
"Deleted their account. Would reload users in a few seconds.",
|
||
);
|
||
|
||
setTimeout(() => {
|
||
getUsers();
|
||
});
|
||
} else {
|
||
toast.error(`${data.reason}`);
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
console.error(
|
||
"There was a problem with deleting the account. Please try again:",
|
||
error,
|
||
);
|
||
toast.error(
|
||
"There was a problem with the delete request. Please try again",
|
||
);
|
||
});
|
||
};
|
||
|
||
const handleVerify2FA = (userId, code) => {
|
||
const data = {
|
||
code: code,
|
||
user_id: userId,
|
||
};
|
||
|
||
fetch(`${globalUrl}/api/v1/users/${userId}/set2fa`, {
|
||
mode: "cors",
|
||
method: "POST",
|
||
body: JSON.stringify(data),
|
||
credentials: "include",
|
||
crossDomain: true,
|
||
withCredentials: true,
|
||
headers: {
|
||
"Content-Type": "application/json; charset=utf-8",
|
||
},
|
||
})
|
||
.then((response) => {
|
||
if (response.status === 200) {
|
||
} else {
|
||
//toast("Wrong code sent.")
|
||
//toast("Wrong code sent. Please try again.")
|
||
}
|
||
|
||
return response.json();
|
||
})
|
||
.then((responseJson) => {
|
||
if (responseJson.success === true) {
|
||
toast("Successfully enabled 2fa");
|
||
|
||
setTimeout(() => {
|
||
getUsers();
|
||
|
||
setImage2FA("");
|
||
setValue2FA("");
|
||
setSecret2FA("");
|
||
setShow2faSetup(false);
|
||
setSelectedUserModalOpen(false);
|
||
}, 1000);
|
||
} else {
|
||
toast("Wrong code sent. Please try again.");
|
||
//toast("Failed setting 2fa: ", responseJson.reason)
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
toast("Wrong code sent. Please try again.");
|
||
//toast("Err: " + error.toString())
|
||
});
|
||
};
|
||
|
||
const get2faCode = (userId) => {
|
||
fetch(`${globalUrl}/api/v1/users/${userId}/get2fa`, {
|
||
method: "GET",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
Accept: "application/json",
|
||
},
|
||
credentials: "include",
|
||
})
|
||
.then((response) => {
|
||
if (response.status !== 200) {
|
||
console.log("Status not 200 for apps :O!");
|
||
}
|
||
|
||
return response.json();
|
||
})
|
||
.then((responseJson) => {
|
||
//console.log("RESPONSE: ", responseJson)
|
||
if (responseJson.success === true) {
|
||
//toast(responseJson.reason)
|
||
setImage2FA(responseJson.reason);
|
||
setSecret2FA(responseJson.extra);
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
toast(error.toString());
|
||
});
|
||
};
|
||
|
||
const generateApikey = (user) => {
|
||
const userId = user.id;
|
||
const data = { user_id: userId };
|
||
|
||
toast("Generating new API key");
|
||
|
||
var fetchdata = {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
Accept: "application/json",
|
||
},
|
||
credentials: "include",
|
||
};
|
||
|
||
if (userId === userdata.id) {
|
||
fetchdata.method = "GET";
|
||
} else {
|
||
fetchdata.body = JSON.stringify(data);
|
||
}
|
||
|
||
fetch(globalUrl + "/api/v1/generateapikey", fetchdata)
|
||
.then((response) => {
|
||
if (response.status !== 200) {
|
||
console.log("Status not 200 for WORKFLOW EXECUTION :O!");
|
||
} else {
|
||
getUsers();
|
||
}
|
||
|
||
return response.json();
|
||
})
|
||
.then((responseJson) => {
|
||
console.log("RESP: ", responseJson);
|
||
if (!responseJson.success && responseJson.reason !== undefined) {
|
||
toast("Failed getting new: " + responseJson.reason);
|
||
} else {
|
||
toast("Got new API key");
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
console.log(error);
|
||
});
|
||
};
|
||
|
||
const UpdateMFAInUserOrg = (org_id) => {
|
||
|
||
handleEditOrg(
|
||
selectedOrganization?.name,
|
||
selectedOrganization?.description,
|
||
selectedOrganization?.id,
|
||
selectedOrganization?.image,
|
||
{
|
||
app_download_repo: selectedOrganization?.defaults?.app_download_repo,
|
||
app_download_branch: selectedOrganization?.defaults?.app_download_branch,
|
||
workflow_download_repo: selectedOrganization?.defaults?.workflow_download_repo,
|
||
workflow_download_branch: selectedOrganization?.defaults?.workflow_download_branch,
|
||
notification_workflow: selectedOrganization?.defaults?.notification_workflow,
|
||
documentation_reference: selectedOrganization?.defaults?.documentation_reference,
|
||
workflow_upload_repo: selectedOrganization?.defaults?.workflow_upload_repo,
|
||
workflow_upload_branch: selectedOrganization?.defaults?.workflow_upload_branch,
|
||
workflow_upload_username: selectedOrganization?.defaults?.workflow_upload_username,
|
||
workflow_upload_token: selectedOrganization?.defaults?.workflow_upload_token,
|
||
newsletter: selectedOrganization?.defaults?.newsletter,
|
||
weekly_recommendations: selectedOrganization?.defaults?.weekly_recommendations,
|
||
},
|
||
{
|
||
sso_entrypoint: selectedOrganization?.sso_config?.sso_entrypoint,
|
||
sso_certificate: selectedOrganization?.sso_config?.sso_certificate,
|
||
client_id: selectedOrganization?.sso_config?.client_id,
|
||
client_secret: selectedOrganization?.sso_config?.client_secret,
|
||
openid_authorization: selectedOrganization?.sso_config?.openid_authorization,
|
||
openid_token: selectedOrganization?.sso_config?.openid_token,
|
||
SSORequired: selectedOrganization?.sso_config?.SSORequired,
|
||
auto_provision: selectedOrganization?.sso_config?.auto_provision,
|
||
},
|
||
[],
|
||
{
|
||
mfa_required: !MFARequired
|
||
}
|
||
);
|
||
setMFARequired((prev)=> !prev)
|
||
}
|
||
|
||
const modalView = (
|
||
<Dialog
|
||
open={modalOpen}
|
||
onClose={() => {
|
||
setModalOpen(false);
|
||
}}
|
||
PaperProps={{
|
||
sx: {
|
||
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
|
||
border: theme?.palette?.DialogStyle?.border,
|
||
minWidth: '440px',
|
||
fontFamily: theme?.typography?.fontFamily,
|
||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||
zIndex: 1000,
|
||
'& .MuiDialogContent-root': {
|
||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||
},
|
||
'& .MuiDialogTitle-root': {
|
||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||
},
|
||
}
|
||
}}
|
||
>
|
||
<DialogTitle>
|
||
<Typography variant="h5" style={{ textTransform: 'none', fontSize: 24 }}>
|
||
Add user
|
||
</Typography>
|
||
</DialogTitle>
|
||
<DialogContent>
|
||
<Typography variant="body1" style={{ marginBottom: 10 }}>
|
||
We will send an email to invite them to your organization.
|
||
</Typography>
|
||
<div>
|
||
Username
|
||
<TextField
|
||
color="primary"
|
||
style={{ backgroundColor: theme.palette.textFieldStyle.backgroundColor }}
|
||
autoFocus
|
||
InputProps={{
|
||
style: {
|
||
height: "50px",
|
||
color: theme.palette.textFieldStyle.color,
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
required
|
||
fullWidth={true}
|
||
autoComplete="username"
|
||
placeholder="username@example.com"
|
||
id="emailfield"
|
||
margin="normal"
|
||
variant="outlined"
|
||
onKeyUp={(e)=>{
|
||
if(e.key === "Enter"){
|
||
if (isCloud) {
|
||
inviteUser(modalUser);
|
||
} else {
|
||
submitUser(modalUser);
|
||
}
|
||
}
|
||
}}
|
||
onChange={(event) =>
|
||
changeModalData("Username", event.target.value)
|
||
}
|
||
/>
|
||
{isCloud ? null : (
|
||
<span>
|
||
Password
|
||
<TextField
|
||
color="primary"
|
||
style={{ backgroundColor: theme.palette.textFieldStyle.backgroundColor }}
|
||
InputProps={{
|
||
style: {
|
||
height: "50px",
|
||
color: theme.palette.textFieldStyle.color,
|
||
fontSize: "1em",
|
||
},
|
||
}}
|
||
required
|
||
fullWidth={true}
|
||
autoComplete="password"
|
||
type="password"
|
||
placeholder="********"
|
||
id="pwfield"
|
||
margin="normal"
|
||
variant="outlined"
|
||
onKeyUp={(e)=>{
|
||
if(e.key === "enter"){
|
||
if (isCloud) {
|
||
inviteUser(modalUser);
|
||
} else {
|
||
submitUser(modalUser);
|
||
}
|
||
}
|
||
}}
|
||
onChange={(event) =>
|
||
changeModalData("Password", event.target.value)
|
||
}
|
||
/>
|
||
</span>
|
||
)}
|
||
</div>
|
||
{loginInfo}
|
||
</DialogContent>
|
||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', p: 2, backgroundColor: theme.palette.platformColor }}>
|
||
<Button
|
||
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16, marginRight: 5, color: theme.palette.primary.main }}
|
||
onClick={() => setModalOpen(false)}
|
||
>
|
||
Cancel
|
||
</Button>
|
||
<Button
|
||
variant="contained"
|
||
color="primary"
|
||
style={{ borderRadius: "2px", textTransform: 'none', fontSize:16,}}
|
||
onClick={() => {
|
||
if (isCloud) {
|
||
inviteUser(modalUser);
|
||
} else {
|
||
submitUser(modalUser);
|
||
}
|
||
}}
|
||
>
|
||
Submit
|
||
</Button>
|
||
</Box>
|
||
</Dialog>
|
||
);
|
||
|
||
const run2FASetup = (data) => {
|
||
if (!show2faSetup) {
|
||
get2faCode(data.id);
|
||
} else {
|
||
// Should remove?
|
||
setImage2FA("");
|
||
setSecret2FA("");
|
||
}
|
||
|
||
setShow2faSetup(!show2faSetup);
|
||
//setShow2faSetup(true);
|
||
};
|
||
|
||
const editUserModal = (
|
||
<Dialog
|
||
open={selectedUserModalOpen}
|
||
onClose={() => {
|
||
setSelectedUserModalOpen(false);
|
||
setImage2FA("");
|
||
setValue2FA("");
|
||
setSecret2FA("");
|
||
setShow2faSetup(false);
|
||
}}
|
||
PaperProps={{
|
||
sx: {
|
||
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
|
||
border: theme?.palette?.DialogStyle?.border,
|
||
fontFamily: theme?.typography?.fontFamily,
|
||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||
zIndex: 1000,
|
||
minWidth: "800px",
|
||
minHeight: "320px",
|
||
overflow: "hidden",
|
||
'& .MuiDialogContent-root': {
|
||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||
},
|
||
'& .MuiDialogTitle-root': {
|
||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||
},
|
||
}
|
||
}}
|
||
>
|
||
<DialogTitle style={{ maxWidth: "800px", width: "100%", textAlign: "center", margin: "auto", backgroundColor: theme?.palette?.DialogStyle?.backgroundColor}}>
|
||
<span style={{ color: theme.palette.text.primary, backgroundColor: theme?.palette?.DialogStyle?.backgroundColor }}>
|
||
<EditIcon style={{ marginTop: 5 }} /> Editing {selectedUser.username}
|
||
</span>
|
||
</DialogTitle>
|
||
<DialogContent>
|
||
{isCloud ? null : (
|
||
<div style={{ display: "flex" }}>
|
||
<TextField
|
||
style={{
|
||
marginTop: 0,
|
||
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
|
||
flex: 3,
|
||
marginRight: 10,
|
||
}}
|
||
InputProps={{
|
||
style: {
|
||
height: 50,
|
||
color: theme.palette.textFieldStyle.color,
|
||
},
|
||
}}
|
||
color="primary"
|
||
required
|
||
fullWidth={true}
|
||
placeholder="New username"
|
||
type="text"
|
||
id="standard-required"
|
||
autoComplete="username"
|
||
margin="normal"
|
||
variant="outlined"
|
||
defaultValue={selectedUser.username}
|
||
onChange={(e) => {
|
||
setNewUsername(e.target.value);
|
||
}}
|
||
/>
|
||
<Button
|
||
style={{ maxHeight: 50, flex: 1 }}
|
||
variant="outlined"
|
||
color="primary"
|
||
disabled={selectedUser.role === "admin"}
|
||
onClick={() => {
|
||
setUser(selectedUser.id, "username", newUsername);
|
||
}}
|
||
>
|
||
Submit
|
||
</Button>
|
||
</div>
|
||
)}
|
||
|
||
{isCloud ? null : (
|
||
<div style={{ display: "flex" }}>
|
||
<TextField
|
||
style={{
|
||
marginTop: 0,
|
||
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
|
||
flex: 3,
|
||
marginRight: 10,
|
||
}}
|
||
InputProps={{
|
||
style: {
|
||
height: 50,
|
||
color: theme.palette.textFieldStyle.color,
|
||
},
|
||
}}
|
||
color="primary"
|
||
required
|
||
fullWidth={true}
|
||
placeholder="New password"
|
||
type="password"
|
||
id="standard-required"
|
||
autoComplete="password"
|
||
margin="normal"
|
||
variant="outlined"
|
||
onChange={(e) => setNewPassword(e.target.value)}
|
||
/>
|
||
<Button
|
||
style={{ maxHeight: 50, flex: 1 }}
|
||
variant="outlined"
|
||
color="primary"
|
||
disabled={selectedUser.role === "admin"}
|
||
onClick={() => onPasswordChange()}
|
||
>
|
||
Submit
|
||
</Button>
|
||
</div>
|
||
)}
|
||
|
||
{userOrgEdit}
|
||
<Divider
|
||
style={{
|
||
marginTop: 20,
|
||
marginBottom: 20,
|
||
backgroundColor: theme.palette.inputColor,
|
||
}}
|
||
/>
|
||
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', margin: "auto", maxWidth: 450 }}>
|
||
<div style={{ display: "flex", justifyContent: "space-between", width: "100%" }}>
|
||
<Button
|
||
style={{textTransform: 'none', fontSize: 16, whiteSpace: 'nowrap', textWrap: 'nowarp'}}
|
||
variant="outlined"
|
||
color="primary"
|
||
disabled={selectedUser.username === userdata.username}
|
||
onClick={() => {
|
||
deleteUser(selectedUser);
|
||
setSelectedUserModalOpen(false);
|
||
}}
|
||
>
|
||
{selectedUser.active ? "Delete from org" : "Delete from org"}
|
||
</Button>
|
||
<Button
|
||
style={{ textTransform: 'none', fontSize: 16, whiteSpace: 'nowrap', textWrap: 'nowarp', }}
|
||
variant="outlined"
|
||
color="primary"
|
||
disabled={
|
||
selectedUser.role === "admin" &&
|
||
selectedUser.username !== userdata.username
|
||
}
|
||
onClick={() => generateApikey(selectedUser)}
|
||
>
|
||
Renew API-key
|
||
</Button>
|
||
<Button
|
||
onClick={() => {
|
||
run2FASetup(userdata);
|
||
}}
|
||
disabled={
|
||
selectedUser.role === "admin" &&
|
||
selectedUser.username !== userdata.username
|
||
}
|
||
variant="outlined"
|
||
color="primary"
|
||
style={{textTransform: 'none', fontSize: 16, whiteSpace: 'nowrap', textWrap: 'nowarp', }}
|
||
>
|
||
{selectedUser.mfa_info !== undefined &&
|
||
selectedUser.mfa_info !== null &&
|
||
selectedUser.mfa_info.active === true
|
||
? "Disable 2FA"
|
||
: "Enable 2FA"}
|
||
</Button>
|
||
</div>
|
||
|
||
{isCloud && userdata.support && selectedUser.id !== userdata.id ? (
|
||
<Button
|
||
style={{
|
||
width: "100%",
|
||
height: 60,
|
||
marginTop: 50,
|
||
border: "1px solid #d52b2b",
|
||
textTransform: "none",
|
||
whiteSpace: 'nowrap',
|
||
textWrap: 'nowarp',
|
||
color:
|
||
showDeleteAccountTextbox === true &&
|
||
deleteAccountText?.length > 0 &&
|
||
deleteAccountText === selectedUser?.username
|
||
? "white"
|
||
: "#d52b2b",
|
||
backgroundColor:
|
||
showDeleteAccountTextbox === true &&
|
||
deleteAccountText?.length > 0 &&
|
||
deleteAccountText === selectedUser.username
|
||
? "#d52b2b"
|
||
: "transparent",
|
||
}}
|
||
disabled={
|
||
showDeleteAccountTextbox === false
|
||
? false
|
||
: showDeleteAccountTextbox === true &&
|
||
deleteAccountText?.length > 0 &&
|
||
deleteAccountText === selectedUser.username
|
||
? false
|
||
: true
|
||
}
|
||
onClick={() => {
|
||
if (
|
||
deleteAccountText?.length > 0 &&
|
||
deleteAccountText === selectedUser?.username
|
||
) {
|
||
console.log("Should delete: ", selectedUser.username);
|
||
handleDeleteAccount(selectedUser.id);
|
||
|
||
setShowDeleteAccountTextbox(false);
|
||
setDeleteAccountText("");
|
||
setSelectedUserModalOpen(false);
|
||
} else {
|
||
setShowDeleteAccountTextbox(true);
|
||
}
|
||
}}
|
||
>
|
||
Delete Account Permanently <br />
|
||
(support users - confirmation needed)
|
||
</Button>
|
||
) : null}
|
||
|
||
{showDeleteAccountTextbox ? (
|
||
<TextField
|
||
style={{
|
||
marginTop: 10,
|
||
}}
|
||
InputProps={{
|
||
style: {
|
||
height: 50,
|
||
color: theme.palette.textFieldStyle.color,
|
||
},
|
||
}}
|
||
color="primary"
|
||
required
|
||
fullWidth={true}
|
||
label="Type the users' username"
|
||
placeholder="username@example.com"
|
||
value={deleteAccountText}
|
||
onChange={(e) => {
|
||
setDeleteAccountText(e.target.value);
|
||
}}
|
||
/>
|
||
) : null}
|
||
</div>
|
||
{show2faSetup ? (
|
||
<div
|
||
style={{
|
||
margin: "auto",
|
||
maxWidth: 300,
|
||
minWidth: 300,
|
||
marginTop: 25,
|
||
}}
|
||
>
|
||
{/*<Divider style={{marginTop: 20, marginBottom: 20}} />*/}
|
||
|
||
{secret2FA !== undefined &&
|
||
secret2FA !== null &&
|
||
secret2FA?.length > 0 ? (
|
||
<span>
|
||
<Typography variant="body2" color="textSecondary">
|
||
Scan the image below with the two-factor authentication app on
|
||
your phone. If you can’t use a QR code, use the code{" "}
|
||
{secret2FA} instead.
|
||
</Typography>
|
||
</span>
|
||
) : null}
|
||
{image2FA !== undefined &&
|
||
image2FA !== null &&
|
||
image2FA?.length > 0 ? (
|
||
<img
|
||
alt={"2 factor img"}
|
||
src={image2FA}
|
||
style={{
|
||
margin: "auto",
|
||
marginTop: 25,
|
||
maxHeight: 200,
|
||
maxWidth: 200,
|
||
minWidth: 200,
|
||
maxWidth: 200,
|
||
}}
|
||
/>
|
||
) : (
|
||
<CircularProgress />
|
||
)}
|
||
|
||
<Typography variant="body2" color="textSecondary">
|
||
After scanning the QR code image, the app will display a code that
|
||
you can enter below.
|
||
</Typography>
|
||
<div style={{ display: "flex" }}>
|
||
<TextField
|
||
color="primary"
|
||
style={{
|
||
flex: 2,
|
||
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
|
||
marginRight: 10,
|
||
}}
|
||
InputProps={{
|
||
style: {
|
||
height: 50,
|
||
color: theme.palette.textFieldStyle.color,
|
||
fontSize: "1em",
|
||
},
|
||
maxLength: 6,
|
||
}}
|
||
required
|
||
fullWidth={true}
|
||
id="2fa_key"
|
||
margin="normal"
|
||
placeholder="6-digit code"
|
||
variant="outlined"
|
||
onChange={(event) => {
|
||
if (event.target.value.length > 6) {
|
||
return;
|
||
}
|
||
|
||
setValue2FA(event.target.value);
|
||
}}
|
||
/>
|
||
<Button
|
||
disabled={value2FA?.length !== 6}
|
||
variant="contained"
|
||
style={{ marginTop: 15, height: 50, flex: 1 }}
|
||
onClick={() => {
|
||
handleVerify2FA(userdata.id, value2FA);
|
||
}}
|
||
color="primary"
|
||
>
|
||
Submit
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
) : null}
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
|
||
const getLogs = (ip, userId) => {
|
||
setLogsLoading(true);
|
||
console.log("logs loading: ", logsLoading);
|
||
fetch(`${globalUrl}/api/v1/users/${userId}/audit?user_ip=${ip}`, {
|
||
mode: "cors",
|
||
method: "GET",
|
||
credentials: "include",
|
||
crossDomain: true,
|
||
withCredentials: true,
|
||
headers: {
|
||
"Content-Type": "application/json; charset=utf-8",
|
||
},
|
||
})
|
||
.then((response) => {
|
||
return response.json();
|
||
})
|
||
.then((responseJson) => {
|
||
console.log("ResponseJSON: ", responseJson);
|
||
if (responseJson.success === true) {
|
||
setLogs(responseJson.logs);
|
||
} else {
|
||
if (
|
||
responseJson.success === false ||
|
||
responseJson.reason !== undefined
|
||
) {
|
||
console.log("Reason given: ", responseJson.reason);
|
||
toast("Failed getting logs: " + responseJson.reason);
|
||
setLogs([]);
|
||
} else {
|
||
toast("Failed getting logs");
|
||
}
|
||
}
|
||
console.log("logs loading now: ", logsLoading);
|
||
setLogsLoading(false);
|
||
})
|
||
.catch((error) => {
|
||
console.log("Error: ", error);
|
||
toast("Failed getting logs. Please contact: ", error);
|
||
console.log("logs loading now: ", logsLoading);
|
||
setLogsLoading(false);
|
||
});
|
||
};
|
||
|
||
const logview = logsViewModal ? (
|
||
<Dialog
|
||
open={logsViewModal}
|
||
onClose={() => {
|
||
setLogsViewModal(false);
|
||
}}
|
||
PaperProps={{
|
||
sx: {
|
||
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
|
||
border: theme?.palette?.DialogStyle?.border,
|
||
minWidth: "1200px",
|
||
minHeight: "320px",
|
||
fontFamily: theme?.typography?.fontFamily,
|
||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||
zIndex: 1000,
|
||
'& .MuiDialogContent-root': {
|
||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||
},
|
||
'& .MuiDialogTitle-root': {
|
||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||
},
|
||
'& .MuiDialogActions-root': {
|
||
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
||
},
|
||
},
|
||
}}
|
||
>
|
||
<DialogTitle>
|
||
<span style={{ color: theme.palette.text.primary }}>User Logs</span>
|
||
</DialogTitle>
|
||
<DialogContent>
|
||
{/* ask user for which IP they want to see logs for by iterating of user.login_info */}
|
||
<FormControl fullWidth>
|
||
<InputLabel
|
||
style={{ size: 10 }}
|
||
id="user-ip-simple-select-label"
|
||
>
|
||
User IP
|
||
</InputLabel>
|
||
|
||
<Select
|
||
labelId="user-ip-simple-select-label"
|
||
id="user-ip-simple-select"
|
||
onChange={(event) => {
|
||
setIpSelected(event.target.value);
|
||
getLogs(event.target.value, userLogViewing.id);
|
||
|
||
|
||
}}
|
||
>
|
||
{(() => {
|
||
const uniqueIPs = new Set();
|
||
|
||
return userLogViewing.login_info.map((data, index) => {
|
||
if (
|
||
data.ip.includes("127.0.0.1") ||
|
||
uniqueIPs.has(data.ip)
|
||
) {
|
||
return null;
|
||
}
|
||
|
||
uniqueIPs.add(data.ip);
|
||
|
||
return (
|
||
<MenuItem key={index} value={data.ip}>
|
||
{data.ip}
|
||
</MenuItem>
|
||
);
|
||
});
|
||
})()}
|
||
</Select>
|
||
</FormControl>
|
||
|
||
{logsLoading && ipSelected.length !== 0 ? (
|
||
<div
|
||
style={{
|
||
marginTop: 20,
|
||
marginBottom: 20,
|
||
display: "flex",
|
||
alignItems: "center",
|
||
}}
|
||
>
|
||
<CircularProgress style={{ marginRight: 10 }} />
|
||
<Typography>Loading logs</Typography>
|
||
</div>
|
||
) : null}
|
||
|
||
<List>
|
||
<ListItem>
|
||
<ListItemText
|
||
primary={
|
||
"Timestamp"
|
||
}
|
||
style={{
|
||
minWidth: 200,
|
||
maxWidth: 200,
|
||
}}
|
||
/>
|
||
<ListItemText
|
||
primary={"Referer"}
|
||
style={{
|
||
minWidth: 300,
|
||
maxWidth: 300,
|
||
overflow: "hidden",
|
||
}}
|
||
/>
|
||
<ListItemText
|
||
primary={"URL"}
|
||
style={{
|
||
minWidth: 700,
|
||
maxWidth: 700,
|
||
overflow: "hidden",
|
||
marginLeft: 10,
|
||
}}
|
||
/>
|
||
</ListItem>
|
||
{logs.map((data, index) => {
|
||
//console.log("LOG: ", data)
|
||
|
||
return (
|
||
// redirect user to logs
|
||
// using request id or trace id
|
||
<ListItem
|
||
key={index}
|
||
style={{
|
||
backgroundColor: index % 2 === 0 ? "#1f2023" : "#27292d",
|
||
}}
|
||
>
|
||
<ListItemText
|
||
primary={new Date(
|
||
data.timestamp * 1000,
|
||
).toLocaleString("en-US", {
|
||
year: "numeric",
|
||
month: "2-digit",
|
||
day: "2-digit",
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
second: "2-digit",
|
||
hour12: false,
|
||
})}
|
||
style={{
|
||
minWidth: 200,
|
||
maxWidth: 200,
|
||
}}
|
||
/>
|
||
<ListItemText
|
||
primary={data.referer}
|
||
style={{
|
||
minWidth: 300,
|
||
maxWidth: 300,
|
||
overflow: "hidden",
|
||
}}
|
||
/>
|
||
<ListItemText
|
||
primary={data.url}
|
||
style={{
|
||
minWidth: 700,
|
||
maxWidth: 700,
|
||
overflow: "hidden",
|
||
marginLeft: 10,
|
||
}}
|
||
/>
|
||
</ListItem>
|
||
)})}
|
||
</List>
|
||
</DialogContent>
|
||
</Dialog>
|
||
) : null
|
||
|
||
return (
|
||
<div style={{ width: "100%", minHeight: 1100, boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: theme.palette.platformColor, borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: "1px solid #494949", }}>
|
||
{modalView}
|
||
{editUserModal}
|
||
{logview}
|
||
<div style={{ height: "100%", maxHeight: 1700, overflowY: "auto", scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin'}}>
|
||
<div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin' }}>
|
||
<div style={{display: 'flex', justifyContent: 'space-between'}}>
|
||
<div>
|
||
<div style={{ marginBottom: 20 }}>
|
||
<Typography variant="h5" style={{ marginBottom: 8, marginTop: 0, }}>User Management</Typography>
|
||
<Typography variant="body2" color="textSecondary">
|
||
Add, edit, distribute or remove users from your organization.{" "}
|
||
<a
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
href="/admin?admin_tab=sso"
|
||
style={{ color: theme.palette.linkColor }}
|
||
>
|
||
Configure SSO
|
||
</a>
|
||
|
||
or
|
||
|
||
<a
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
href="/docs/organizations#user_management"
|
||
style={{ color: theme.palette.linkColor }}
|
||
>
|
||
learn more about users
|
||
</a>
|
||
</Typography>
|
||
</div>
|
||
<div />
|
||
<Button
|
||
style={{ fontSize: 16, textTransform: 'none', borderRadius: 4, width: 162, height: 40, boxShadow: 'none' }}
|
||
variant="contained"
|
||
color="primary"
|
||
onClick={() => setModalOpen(true)}
|
||
>
|
||
Add user
|
||
</Button>
|
||
<Button
|
||
style={{ boxShadow: 'none', borderRadius: 4, width: 81, height: 40, marginLeft: 16, marginRight: 15 }}
|
||
variant="contained"
|
||
color="secondary"
|
||
onClick={() => getUsers()}
|
||
>
|
||
<CachedIcon />
|
||
</Button>
|
||
</div>
|
||
<div>
|
||
<Typography variant="body1">MFA Required</Typography>
|
||
<Switch
|
||
checked={MFARequired}
|
||
onChange={(event) => {
|
||
UpdateMFAInUserOrg(selectedOrganization.id);
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div
|
||
style={{
|
||
borderRadius: 4,
|
||
marginTop: 24,
|
||
border: theme.palette.defaultBorder,
|
||
width: "100%",
|
||
overflowX: "auto",
|
||
paddingBottom: 0,
|
||
}}
|
||
>
|
||
<List
|
||
style={{
|
||
width: '100%',
|
||
tableLayout: "auto",
|
||
display: "table",
|
||
minWidth: 800,
|
||
overflowX: "auto",
|
||
paddingBottom: 0,
|
||
}}
|
||
>
|
||
<ListItem style={{ width: "100%", padding: "10px 10px 10px 0px", verticalAlign: 'middle', borderBottom: theme.palette.defaultBorder, display: "table-row" }}>
|
||
{[...(isCloud ? ["Region"] : []), "Username", /*"API Key",*/ "Role", /*"Active",*/ "Type", "MFA", ...(selectedOrganization?.child_orgs?.length > 0 ? ["Suborgs"]: []), "Actions", "Last Login"].map((header, index) => (
|
||
<ListItemText
|
||
key={index}
|
||
primary={header}
|
||
style={{
|
||
display: "table-cell",
|
||
padding: index === 0 ? "0px 8px 8px 15px": "0px 8px 8px 8px",
|
||
whiteSpace: "nowrap",
|
||
textOverflow: "ellipsis",
|
||
borderBottom: theme.palette.defaultBorder,
|
||
position: "sticky",
|
||
verticalAlign: "middle",
|
||
}}
|
||
/>
|
||
))}
|
||
</ListItem>
|
||
{showLoader ? (
|
||
[...Array(6)].map((_, rowIndex) => (
|
||
<ListItem
|
||
key={rowIndex}
|
||
style={{
|
||
display: "table-row",
|
||
backgroundColor: theme.palette.platformColor,
|
||
}}
|
||
>
|
||
{Array(isCloud ? 7 : 6)
|
||
.fill()
|
||
.map((_, colIndex) => (
|
||
<ListItemText
|
||
key={colIndex}
|
||
style={{
|
||
display: "table-cell",
|
||
padding: "8px",
|
||
}}
|
||
>
|
||
<Skeleton
|
||
variant="text"
|
||
animation="wave"
|
||
sx={{
|
||
backgroundColor: theme.palette.loaderColor,
|
||
height: "20px",
|
||
borderRadius: "4px",
|
||
}}
|
||
/>
|
||
</ListItemText>
|
||
))}
|
||
</ListItem>
|
||
))
|
||
): users === 0 ? null
|
||
: users?.map((data, index) => {
|
||
var bgColor = themeMode === "dark" ? "#212121" : "#FFFFFF";
|
||
if (index % 2 === 0) {
|
||
bgColor = themeMode === "dark" ? "#1A1A1A" : "#EAEAEA";
|
||
}
|
||
|
||
const timeNow = new Date().getTime();
|
||
|
||
// Get the highest timestamp in data.login_info
|
||
var lastLogin = "N/A";
|
||
if (data.login_info !== undefined && data.login_info !== null) {
|
||
var loginInfo = 0;
|
||
for (var i = 0; i < data?.login_info?.length; i++) {
|
||
if (data.login_info[i].timestamp > loginInfo) {
|
||
loginInfo = data.login_info[i].timestamp;
|
||
}
|
||
}
|
||
|
||
if (loginInfo > 0) {
|
||
lastLogin =
|
||
new Date(loginInfo * 1000).toISOString().slice(0, 10) +
|
||
" (" +
|
||
data?.login_info?.length +
|
||
")";
|
||
}
|
||
}
|
||
|
||
var userData = data.username;
|
||
if (userdata.support === true) {
|
||
userData = (
|
||
<a
|
||
style={{
|
||
cursor: "pointer",
|
||
textDecoration: "underline",
|
||
color: theme.palette.linkColor,
|
||
}}
|
||
onClick={() => {
|
||
setLogsViewModal(true);
|
||
setUserLogViewing(data);
|
||
|
||
if (userLogViewing.login_info !== undefined && userLogViewing.login_info !== null && userLogViewing.login_info.length > 0) {
|
||
getLogs(userLogViewing.login_info[0].ip, userLogViewing.id)
|
||
setIpSelected(userLogViewing.login_info[0].ip);
|
||
}
|
||
}}
|
||
>
|
||
{data.username}
|
||
</a>
|
||
);
|
||
}
|
||
|
||
|
||
const userRegion = data?.user_geo_info?.country?.iso_code
|
||
|
||
return (
|
||
<ListItem key={index} style={{ backgroundColor: bgColor, display: 'table-row', borderBottomLeftRadius: users?.length - 1 === index ? 8 : 0, borderBottomRightRadius: users?.length - 1 === index ? 8 : 0 }}>
|
||
{isCloud ? (
|
||
<ListItemText
|
||
primary={(
|
||
userRegion ? (
|
||
<img src={`https://flagcdn.com/48x36/${userRegion.toLowerCase()}.png`} alt={data?.user_geo_info?.country?.iso_code} style={{ marginRight: 30, width: 25, height: 23, }} />
|
||
) : null
|
||
)}
|
||
style={{ display: 'table-cell', verticalAlign: 'middle', textAlign: 'center' }}
|
||
/>) : null}
|
||
<ListItemText
|
||
primary={(
|
||
<Tooltip title={data.username || 'No username available'}>
|
||
<span>{userData || 'No username'}</span>
|
||
</Tooltip>
|
||
)}
|
||
primaryTypographyProps={{
|
||
style: {
|
||
maxWidth: 150,
|
||
minWidth: 100,
|
||
width: 'auto',
|
||
color: theme.palette.primary.main,
|
||
textOverflow: "ellipsis",
|
||
whiteSpace: "nowrap",
|
||
overflow: "hidden",
|
||
padding: "8px 8px 8px 15px",
|
||
},
|
||
}}
|
||
style={{display:'table-cell', verticalAlign: 'middle' }}
|
||
/>
|
||
|
||
{/*
|
||
<ListItemText
|
||
style={{
|
||
textAlign: "center",
|
||
display: "flex",
|
||
justifyContent: "center",
|
||
alignItems: "center",
|
||
padding: "8px",
|
||
}}
|
||
primary={
|
||
data.apikey === undefined || data?.apikey?.length === 0 ? (
|
||
""
|
||
) : (
|
||
<Tooltip
|
||
title={"Copy Api Key"}
|
||
aria-label={"Copy APIkey"}
|
||
>
|
||
<IconButton
|
||
onClick={() => {
|
||
navigator.clipboard.writeText(data.apikey);
|
||
toast.success("Apikey copied to clipboard");
|
||
}}
|
||
>
|
||
<ContentCopyOutlinedIcon
|
||
style={{ color: "#D9D9D9" }}
|
||
/>
|
||
</IconButton>
|
||
</Tooltip>
|
||
)
|
||
}
|
||
/>
|
||
*/}
|
||
|
||
<ListItemText
|
||
primary={
|
||
<Select
|
||
SelectDisplayProps={{
|
||
style: {
|
||
// marginLeft: 10,
|
||
},
|
||
}}
|
||
value={data.role}
|
||
onChange={(e) => {
|
||
console.log("VALUE: ", e.target.value);
|
||
setUser(data.id, "role", e.target.value);
|
||
}}
|
||
sx={{
|
||
backgroundColor: theme.palette.backgroundColor,
|
||
color: theme.palette.textColor,
|
||
height: "50px",
|
||
borderRadius: "4px",
|
||
marginTop: "8px",
|
||
marginBottom: "8px",
|
||
padding: "8px",
|
||
}}
|
||
MenuProps={{
|
||
PaperProps: {
|
||
sx: {
|
||
"& .MuiList-root": {
|
||
padding: 0,
|
||
},
|
||
},
|
||
},
|
||
}}
|
||
>
|
||
<MenuItem
|
||
sx={{
|
||
backgroundColor: theme.palette.backgroundColor,
|
||
color: theme.palette.textColor,
|
||
"&:hover": {
|
||
backgroundColor: theme.palette.hoverColor,
|
||
},
|
||
}}
|
||
value={"admin"}
|
||
>
|
||
Org Admin
|
||
</MenuItem>
|
||
<MenuItem
|
||
sx={{
|
||
backgroundColor: theme.palette.backgroundColor,
|
||
color: theme.palette.textColor,
|
||
"&:hover": {
|
||
backgroundColor: theme.palette.hoverColor,
|
||
},
|
||
}}
|
||
value={"user"}
|
||
>
|
||
Org User
|
||
</MenuItem>
|
||
<MenuItem
|
||
sx={{
|
||
backgroundColor: theme.palette.backgroundColor,
|
||
color: theme.palette.textColor,
|
||
"&:hover": {
|
||
backgroundColor: theme.palette.hoverColor,
|
||
},
|
||
}}
|
||
value={"org-reader"}
|
||
>
|
||
Org Reader
|
||
</MenuItem>
|
||
</Select>
|
||
}
|
||
style={{ display:'table-cell', verticalAlign: 'middle' }}
|
||
/>
|
||
|
||
{/*
|
||
<ListItemText
|
||
primary={data.active ? "True" : "False"}
|
||
style={{display:'table-cell',verticalAlign: 'middle' , padding: "8px", textAlign: "center", color: data.active ? "#02CB70" : "#F53434" }}
|
||
/>
|
||
*/}
|
||
|
||
<ListItemText
|
||
primary={
|
||
data.login_type === undefined ||
|
||
data?.login_type === null ||
|
||
data?.login_type?.length === 0
|
||
? "Normal"
|
||
: data.login_type
|
||
}
|
||
style={{ display:'table-cell',verticalAlign: 'middle', padding: "8px", }}
|
||
/>
|
||
|
||
<ListItemText
|
||
primary={
|
||
data?.mfa_info !== undefined &&
|
||
data?.mfa_info !== null &&
|
||
data?.mfa_info.active === true
|
||
? "Active"
|
||
: "Inactive"
|
||
}
|
||
style={{ display:'table-cell', verticalAlign: 'middle',padding: "8px", color: data.mfa_info.active ? "#02CB70" : "#F53434" }}
|
||
/>
|
||
|
||
{selectedOrganization?.child_orgs !== undefined &&
|
||
selectedOrganization?.child_orgs !== null &&
|
||
selectedOrganization?.child_orgs?.length > 0 ? (
|
||
<ListItemText
|
||
style={{ display: "table-cell", verticalAlign: 'middle', padding: "8px", }}
|
||
primary={
|
||
data?.orgs === undefined || data?.orgs === null
|
||
? 0
|
||
: data?.orgs?.length - 1
|
||
}
|
||
primaryTypographyProps={{
|
||
style: {
|
||
marginLeft: 20
|
||
}
|
||
}}
|
||
/>
|
||
) : null}
|
||
<ListItemText style={{ display:'table-cell', textAlign: "left", verticalAlign: 'middle', padding: "8px", }}>
|
||
<IconButton
|
||
onClick={() => {
|
||
setSelectedUserModalOpen(true);
|
||
setSelectedUser(data);
|
||
|
||
// Find matching orgs between current org and current user's access to those orgs
|
||
if (
|
||
userdata?.orgs !== undefined &&
|
||
userdata?.orgs !== null &&
|
||
userdata?.orgs?.length > 0 &&
|
||
selectedOrganization?.child_orgs !== undefined &&
|
||
selectedOrganization?.child_orgs !== null &&
|
||
selectedOrganization?.child_orgs?.length > 0
|
||
) {
|
||
var active = [];
|
||
for (var key in userdata.orgs) {
|
||
const found =
|
||
selectedOrganization.child_orgs.find(
|
||
(item) => item.id === userdata.orgs[key].id
|
||
);
|
||
if (found !== null && found !== undefined) {
|
||
if (
|
||
data.orgs === undefined ||
|
||
data.orgs === null
|
||
) {
|
||
continue;
|
||
}
|
||
|
||
const subfound = data.orgs.find(
|
||
(item) => item === found.id
|
||
);
|
||
if (
|
||
subfound !== null &&
|
||
subfound !== undefined
|
||
) {
|
||
active.push(subfound);
|
||
}
|
||
}
|
||
}
|
||
|
||
setMatchingOrganizations(active);
|
||
}
|
||
}}
|
||
>
|
||
<svg
|
||
width="24"
|
||
height="24"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
>
|
||
<path
|
||
d="M16.1038 4.66848C16.3158 4.45654 16.5674 4.28843 16.8443 4.17373C17.1212 4.05903 17.418 4 17.7177 4C18.0174 4 18.3142 4.05903 18.5911 4.17373C18.868 4.28843 19.1196 4.45654 19.3315 4.66848C19.5435 4.88041 19.7116 5.13201 19.8263 5.40891C19.941 5.68582 20 5.9826 20 6.28232C20 6.58204 19.941 6.87882 19.8263 7.15573C19.7116 7.43263 19.5435 7.68423 19.3315 7.89617L8.43807 18.7896L4 20L5.21038 15.5619L16.1038 4.66848Z"
|
||
stroke={themeMode=== "dark" ? "#F1F1F1" : "#333"}
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
/>
|
||
</svg>
|
||
</IconButton>
|
||
{/* <Button
|
||
onClick={() => {
|
||
generateApikey(data)
|
||
}}
|
||
disabled={data.role === "admin" && data.username !== userdata.username}
|
||
variant="outlined"
|
||
color="primary"
|
||
>
|
||
New apikey
|
||
</Button> */}
|
||
</ListItemText>
|
||
<ListItemText
|
||
style={{ display:'table-cell', verticalAlign: 'middle', padding: "8px", }}
|
||
primary={lastLogin}
|
||
><span />
|
||
</ListItemText>
|
||
</ListItem>
|
||
);
|
||
})}
|
||
</List>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})
|
||
|
||
export default UserManagmentTab;
|