User Management
@@ -14,19 +21,7 @@ function AdminConsole() {
-
-
-
-
-
-
- Data Management
-
- Analyse and manage batches, collections, artefacts and other data that goes into the platform.
-
-
-
-
+
diff --git a/src/pages/ArtefactEditor.jsx b/src/pages/ArtefactEditor.jsx
index 5a297fe..e1f30c5 100644
--- a/src/pages/ArtefactEditor.jsx
+++ b/src/pages/ArtefactEditor.jsx
@@ -1,11 +1,126 @@
-import { Box, Flex } from "@chakra-ui/react";
+import { Box, Flex, Text, Image } from "@chakra-ui/react";
import { useParams } from 'react-router-dom';
+import { useEffect, useRef, useState } from "react";
+import { useSelector } from "react-redux";
+import ToastWizard from "../components/toastWizard";
+import server, { JSONResponse } from "../networking";
+import CentredSpinner from "../components/CentredSpinner";
+import { FaChevronLeft, FaChevronRight } from "react-icons/fa";
+import EditorCard from "../components/DataStudio/editorCard";
+import { FaCircleArrowLeft } from "react-icons/fa6";
function ArtefactEditor() {
const { artID } = useParams();
+ const [metadata, setMetadata] = useState(null);
+ const { loaded } = useSelector(state => state.auth);
+
+ // Metadata fetching function
+ const fetchMetadata = async () => {
+ try {
+ const response = await server.get(`/cdn/artefactMetadata/${artID}`);
+
+ if (response.data instanceof JSONResponse) {
+ if (response.data.isErrorStatus()) {
+ const errObject = {
+ response: {
+ data: response.data
+ }
+ };
+ throw errObject;
+ }
+
+ // Success case
+ setMetadata(response.data.raw.data);
+ } else {
+ throw new Error("Unexpected response format");
+ }
+ } catch (err) {
+ if (err.response && err.response.data instanceof JSONResponse) {
+ console.log("Error response in fetching metadata:", err.response.data.fullMessage());
+ if (err.response.data.userErrorType()) {
+ ToastWizard.standard(
+ "error",
+ "We could not load the details",
+ err.response.data.message || "There was a problem retrieving this item information. Please try again later.",
+ 3000,
+ true,
+ fetchMetadata,
+ 'Retry'
+ );
+ } else {
+ ToastWizard.standard(
+ "error",
+ "Something went wrong",
+ "We are having trouble loading the information. Please try again in a moment.",
+ 3000,
+ true,
+ fetchMetadata,
+ 'Retry'
+ );
+ }
+ } else {
+ console.log("Unexpected error in fetching metadata:", err.response.data.fullMessage());
+ ToastWizard.standard(
+ "error",
+ "Connection issue",
+ "We could not connect to the server. Please check your internet connection or try again later.",
+ 3000,
+ true,
+ fetchMetadata,
+ 'Retry'
+ );
+ }
+ setMetadata(null);
+ }
+ };
+
+ // Fetch metadata if user is loaded
+ useEffect(() => {
+ if (loaded) {
+ fetchMetadata()
+ }
+ }, [loaded]);
+
+ // Show loading spinner while data is being fetched
+ if (!metadata) {
+ return
+ }
+
return (
- ArtefactEditor for {artID}
+
+
+
+
+ Data Studio
+ {metadata.name}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
-export default ArtefactEditor;
+export default ArtefactEditor;
\ No newline at end of file
diff --git a/src/pages/GroupView.jsx b/src/pages/GroupView.jsx
index 575240f..d3fbe4d 100644
--- a/src/pages/GroupView.jsx
+++ b/src/pages/GroupView.jsx
@@ -1,9 +1,9 @@
import { useParams } from 'react-router-dom';
function GroupView() {
- const { artID, colID } = useParams();
+ const { colID } = useParams();
- return GroupView for collection {colID}, {artID}
;
+ return GroupView for collection {colID}
;
}
export default GroupView;
diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx
index 2329c60..3091463 100644
--- a/src/pages/Profile.jsx
+++ b/src/pages/Profile.jsx
@@ -1,23 +1,28 @@
-import { Avatar, Box, Button, CloseButton, Dialog, Field, FileUpload, Flex, Grid, GridItem, HStack, Input, Portal, Skeleton, SkeletonText, Spacer, Spinner, Stack, Text, useMediaQuery, VStack } from '@chakra-ui/react'
+import { Alert, Avatar, Box, Button, CloseButton, Dialog, Field, FileUpload, Flex, Grid, GridItem, HStack, Input, Portal, Skeleton, SkeletonCircle, SkeletonText, Spacer, Spinner, Stack, Text, useMediaQuery, VStack } from '@chakra-ui/react'
import { useEffect, useRef, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux';
-import { useNavigate } from 'react-router-dom';
+import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { withMask } from 'use-mask-input';
import { logout } from '../slices/AuthState';
import ToastWizard from '../components/toastWizard';
import server, { JSONResponse } from '../networking';
-import { GiExitDoor } from 'react-icons/gi';
import { IoLogOutOutline } from 'react-icons/io5';
import ProfileActionBar from '../components/Identity/ProfileActionBar';
import AccountActivityCard from '../components/Identity/AccountActivityCard';
import ChangePasswordDialogButton from '../components/Identity/ChangePasswordDialogButton';
import ProfileAvatar from '../components/Identity/ProfileAvatar';
+import IAMLogoutUserButton from '../components/Identity/IAMLogoutUserButton';
+import IAMResetPasswordButton from '../components/Identity/IAMResetPasswordButton';
+import IAMDeleteAccountButton from '../components/Identity/IAMDeleteAccountButton';
function Profile() {
const navigate = useNavigate();
const dispatch = useDispatch();
+ const { userID } = useParams();
- const { username, accountID } = useSelector(state => state.auth);
+ const { accountID, superuser, loaded } = useSelector(state => state.auth);
+
+ const [targetProfileID, setTargetProfileID] = useState(null);
// Loading states
const [loggingOut, setLoggingOut] = useState(false);
@@ -34,6 +39,11 @@ function Profile() {
const [isSmallerThan800] = useMediaQuery("(max-width: 800px)");
const handleLogout = () => {
+ if (targetProfileID !== accountID) {
+ ToastWizard.standard("error", "You cannot log out another user", "Please go to your own profile to log out.");
+ return;
+ }
+
setLoggingOut(true);
const logoutPromise = new Promise((resolve, reject) => {
dispatch(logout(true, (msg) => {
@@ -89,7 +99,7 @@ function Profile() {
const getAccountInfo = async () => {
setInfoLoading(true);
try {
- const response = await server.get(`/cdn/profileInfo/${accountID}?includeLogs=true`);
+ const response = await server.get(`/cdn/profileInfo/${targetProfileID}?includeLogs=true`);
if (response.data instanceof JSONResponse) {
if (response.data.isErrorStatus()) {
@@ -108,6 +118,7 @@ function Profile() {
setOriginalAccountInfo(preppedAccInfo);
setAccountInfo(preppedAccInfo);
setAuditLogsData(processAuditLogs(response.data.raw.info.logs || []));
+ setInfoLoading(false);
} else {
throw new Error("Unexpected response format");
}
@@ -124,20 +135,24 @@ function Profile() {
ToastWizard.standard("error", "Couldn't retrieve profile.", "Failed to retrieve your information. Please try again.", 3000, true, getAccountInfo, 'Retry');
}
}
-
- setInfoLoading(false);
}
useEffect(() => {
- if (username) {
+ if (targetProfileID) {
getAccountInfo();
}
- }, [])
+ }, [targetProfileID])
- // useEffect(() => {
- // console.log(accountInfo)
- // console.log(auditLogsData)
- // }, [accountInfo, auditLogsData])
+ useEffect(() => {
+ if (loaded) {
+ if (userID && userID !== accountID && !superuser) {
+ console.log("Unauthorised access to superuser protected resource; redirecting...")
+ navigate('/profile');
+ } else {
+ setTargetProfileID(userID || accountID);
+ }
+ }
+ }, [loaded, userID])
const handleEnterKey = (e) => {
if (e.key === 'Enter') {
@@ -147,15 +162,29 @@ function Profile() {
return <>
-
-
-
+
+
+ {targetProfileID !== accountID &&
+
+
+
+ Superuser Access
+
+ You're viewing this profile with superuser privileges. You're allowed to carry out some actions on this profile, but be cautious as changes will affect the user directly.
+
+
+
+ }
+
+
+
+
{fullName}
@@ -165,39 +194,38 @@ function Profile() {
Name & Account Details
-
+
First Name
setAccountInfo({ ...accountInfo, fname: e.target.value })} />
-
-
Last Name
setAccountInfo({ ...accountInfo, lname: e.target.value })} />
-
-
Username
setAccountInfo({ ...accountInfo, username: e.target.value })} />
-
-
Email
setAccountInfo({ ...accountInfo, email: e.target.value })} />
-
-
Contact
setAccountInfo({ ...accountInfo, contact: e.target.value })} />
+
+ {superuser === true && <>
+
+ Role
+ setAccountInfo({ ...accountInfo, role: e.target.value })} />
+
+ >}
@@ -213,20 +241,29 @@ function Profile() {
Security & Authentication
- Last Login: {originalAccountInfo.lastLogin}
+ Last Login: {originalAccountInfo.lastLogin || 'Never'}
-
-
-
-
-
+ {!infoLoading &&
+
+ {targetProfileID === accountID ? <>
+
+
+
+ > : <>
+
+
+
+
+ >}
+
+ }
-
+
>
}
diff --git a/src/pages/UserManagement.jsx b/src/pages/UserManagement.jsx
new file mode 100644
index 0000000..75bc85c
--- /dev/null
+++ b/src/pages/UserManagement.jsx
@@ -0,0 +1,102 @@
+import { Box, SimpleGrid, Skeleton, Text } from '@chakra-ui/react'
+import IAMStatisticBox from '../components/Identity/IAMStatisticBox'
+import IAMUserCard from '../components/Identity/IAMUserCard'
+import { useSelector } from 'react-redux'
+import { useState } from 'react'
+import server, { JSONResponse } from '../networking';
+import ToastWizard from '../components/toastWizard';
+import { useEffect } from 'react'
+import CreateUserDialog from '../components/Identity/CreateUserDialog'
+
+function UserManagement() {
+ const { loaded, superuser } = useSelector(state => state.auth);
+
+ const [userData, setUserData] = useState({});
+ const [retrievingUsers, setRetrievingUsers] = useState(true);
+
+ const retrieveUserData = async () => {
+ setRetrievingUsers(true);
+ try {
+ const response = await server.get('/admin/listUsers')
+
+ if (response.data instanceof JSONResponse) {
+ if (response.data.isErrorStatus()) {
+ const errObject = {
+ response: {
+ data: response.data
+ }
+ };
+ throw new Error(errObject);
+ }
+
+ // Success case
+ var rawData = response.data.raw.users; // dictionary where userID: userDict
+ for (const userID in rawData) {
+ rawData[userID].userID = userID; // add userID to each user object
+ }
+
+ setUserData(rawData);
+ } else {
+ throw new Error("Unexpected response format");
+ }
+ } catch (err) {
+ if (err.response && err.response.data instanceof JSONResponse) {
+ console.log("Error response in login request:", err.response.data.fullMessage());
+ if (err.response.data.userErrorType()) {
+ ToastWizard.standard("error", "Failed to retrieve users.", err.response.data.message);
+ } else {
+ ToastWizard.standard("error", "Something went wrong", "Failed to retrieve users. Please try again later.");
+ }
+ } else {
+ console.log("Unexpected error in login request:", err);
+ ToastWizard.standard("error", "Something went wrong", "Failed to retrieve users. Please try again later.");
+ }
+ } finally {
+ setRetrievingUsers(false);
+ }
+ }
+
+ useEffect(() => {
+ if (loaded && superuser === true) {
+ retrieveUserData();
+ }
+ }, [loaded, superuser]);
+
+ return (
+
+
+ User Management
+
+
+
+
+
+
+
+
+
+ Manage & View Accounts
+
+
+ {retrievingUsers ? (
+
+ ) : (
+ Object.keys(userData).length > 0 ? (
+ Object.values(userData).map((user) => (
+
+ ))
+ ) : (
+ No regular users found.
+ )
+ )}
+
+
+ )
+}
+
+export default UserManagement
\ No newline at end of file