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 ? (
Accessible Sub-Organizations (
{selectedUser?.orgs ? selectedUser?.orgs?.length - 1 : 0})
}
renderValue={(selected) => {
return selected.join(", ");
}}
MenuProps={MenuProps}
>
{selectedOrganization.child_orgs.map((org, index) => (
))}
) : 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 = (
);
const run2FASetup = (data) => {
if (!show2faSetup) {
get2faCode(data.id);
} else {
// Should remove?
setImage2FA("");
setSecret2FA("");
}
setShow2faSetup(!show2faSetup);
//setShow2faSetup(true);
};
const editUserModal = (
);
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 ? (
) : null
return (
{modalView}
{editUserModal}
{logview}
MFA Required
{
UpdateMFAInUserOrg(selectedOrganization.id);
}}
/>
{[...(isCloud ? ["Region"] : []), "Username", /*"API Key",*/ "Role", /*"Active",*/ "Type", "MFA", ...(selectedOrganization?.child_orgs?.length > 0 ? ["Suborgs"]: []), "Actions", "Last Login"].map((header, index) => (
))}
{showLoader ? (
[...Array(6)].map((_, rowIndex) => (
{Array(isCloud ? 7 : 6)
.fill()
.map((_, colIndex) => (
))}
))
): 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 = (
{
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}
);
}
const userRegion = data?.user_geo_info?.country?.iso_code
return (
{isCloud ? (
) : null
)}
style={{ display: 'table-cell', verticalAlign: 'middle', textAlign: 'center' }}
/>) : null}
{userData || 'No username'}
)}
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' }}
/>
{/*
{
navigator.clipboard.writeText(data.apikey);
toast.success("Apikey copied to clipboard");
}}
>
)
}
/>
*/}
{
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,
},
},
},
}}
>
}
style={{ display:'table-cell', verticalAlign: 'middle' }}
/>
{/*
*/}
{selectedOrganization?.child_orgs !== undefined &&
selectedOrganization?.child_orgs !== null &&
selectedOrganization?.child_orgs?.length > 0 ? (
) : null}
{
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);
}
}}
>
{/* */}
);
})}
);
})
export default UserManagmentTab;