Compare commits
3 Commits
d464c9cd71
...
7476c939c3
| Author | SHA1 | Date |
|---|---|---|
|
|
7476c939c3 | |
|
|
e5b910238f | |
|
|
075624717d |
|
|
@ -363,6 +363,38 @@ public class Controller : ControllerBase
|
|||
return user.AccessibleInstallations().ToList();
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetDirectInstallationAccessForUser))]
|
||||
public ActionResult<IEnumerable<Object>> GetDirectInstallationAccessForUser(Int64 userId, Token authToken)
|
||||
{
|
||||
var sessionUser = Db.GetSession(authToken)?.User;
|
||||
if (sessionUser == null)
|
||||
return Unauthorized();
|
||||
|
||||
var user = Db.GetUserById(userId);
|
||||
if (user == null)
|
||||
return Unauthorized();
|
||||
|
||||
return user.DirectlyAccessibleInstallations()
|
||||
.Select(i => new { i.Id, i.Name })
|
||||
.ToList<Object>();
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetDirectFolderAccessForUser))]
|
||||
public ActionResult<IEnumerable<Object>> GetDirectFolderAccessForUser(Int64 userId, Token authToken)
|
||||
{
|
||||
var sessionUser = Db.GetSession(authToken)?.User;
|
||||
if (sessionUser == null)
|
||||
return Unauthorized();
|
||||
|
||||
var user = Db.GetUserById(userId);
|
||||
if (user == null)
|
||||
return Unauthorized();
|
||||
|
||||
return user.DirectlyAccessibleFolders()
|
||||
.Select(f => new { f.Id, f.Name })
|
||||
.ToList<Object>();
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetUsersWithInheritedAccessToInstallation))]
|
||||
public ActionResult<IEnumerable<Object>> GetUsersWithInheritedAccessToInstallation(Int64 id, Token authToken)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -376,7 +376,6 @@ public static class SessionMethods
|
|||
&& sessionUser.HasAccessTo(originalUser)
|
||||
&& editedUser
|
||||
.WithParentOf(originalUser) // prevent moving
|
||||
.WithNameOf(originalUser)
|
||||
.WithPasswordOf(originalUser)
|
||||
.Apply(Db.Update);
|
||||
}
|
||||
|
|
@ -474,13 +473,25 @@ public static class SessionMethods
|
|||
{
|
||||
var sessionUser = session?.User;
|
||||
|
||||
return sessionUser is not null
|
||||
&& installation is not null
|
||||
&& user is not null
|
||||
&& user.IsDescendantOf(sessionUser)
|
||||
&& sessionUser.HasAccessTo(installation)
|
||||
&& user.HasAccessTo(installation)
|
||||
&& Db.InstallationAccess.Delete(a => a.UserId == user.Id && a.InstallationId == installation.Id) > 0;
|
||||
if (sessionUser is null || installation is null || user is null)
|
||||
return false;
|
||||
|
||||
if (!(user.IsDescendantOf(sessionUser) || sessionUser.UserType == 2))
|
||||
return false;
|
||||
|
||||
if (!sessionUser.HasAccessTo(installation) || !user.HasAccessTo(installation))
|
||||
return false;
|
||||
|
||||
// Try direct InstallationAccess record first
|
||||
if (Db.InstallationAccess.Delete(a => a.UserId == user.Id && a.InstallationId == installation.Id) > 0)
|
||||
return true;
|
||||
|
||||
// No direct record — access is inherited via a folder; revoke that folder access
|
||||
var accessFolder = installation.Ancestors()
|
||||
.FirstOrDefault(f => user.HasDirectAccessTo(f));
|
||||
|
||||
return accessFolder is not null
|
||||
&& Db.FolderAccess.Delete(a => a.UserId == user.Id && a.FolderId == accessFolder.Id) > 0;
|
||||
}
|
||||
|
||||
public static Boolean RevokeUserAccessTo(this Session? session, User? user, Folder? folder)
|
||||
|
|
@ -490,7 +501,7 @@ public static class SessionMethods
|
|||
return sessionUser is not null
|
||||
&& folder is not null
|
||||
&& user is not null
|
||||
&& user.IsDescendantOf(sessionUser)
|
||||
&& (user.IsDescendantOf(sessionUser) || sessionUser.UserType == 2)
|
||||
&& sessionUser.HasAccessTo(folder)
|
||||
&& user.HasAccessTo(folder)
|
||||
&& Db.FolderAccess.Delete(a => a.UserId == user.Id && a.FolderId == folder.Id) > 0;
|
||||
|
|
|
|||
|
|
@ -40,9 +40,8 @@ public static partial class Db
|
|||
var originalUser = GetUserById(user.Id);
|
||||
if (originalUser is null) return false;
|
||||
|
||||
// these columns must not be modified!
|
||||
// ParentId must not be modified via this method
|
||||
user.ParentId = originalUser.ParentId;
|
||||
user.Name = originalUser.Name;
|
||||
|
||||
return Update(obj: user);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,6 +175,9 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
return true;
|
||||
};
|
||||
|
||||
const canEdit = currentUser.userType === UserType.admin;
|
||||
const isPartner = currentUser.userType === UserType.partner;
|
||||
|
||||
return (
|
||||
<>
|
||||
{openModalDeleteInstallation && (
|
||||
|
|
@ -276,6 +279,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
inputProps={{ readOnly: !canEdit }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -288,8 +292,9 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
required
|
||||
error={formValues.region === ''}
|
||||
required={canEdit}
|
||||
error={canEdit && formValues.region === ''}
|
||||
inputProps={{ readOnly: !canEdit }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -305,8 +310,9 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
required
|
||||
error={formValues.location === ''}
|
||||
required={canEdit}
|
||||
error={canEdit && formValues.location === ''}
|
||||
inputProps={{ readOnly: !canEdit }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -319,11 +325,13 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
required
|
||||
error={formValues.country === ''}
|
||||
required={canEdit}
|
||||
error={canEdit && formValues.country === ''}
|
||||
inputProps={{ readOnly: !canEdit }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{canEdit && (
|
||||
<div>
|
||||
<TextField
|
||||
label={
|
||||
|
|
@ -336,6 +344,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<FormControl
|
||||
|
|
@ -362,6 +371,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
name="device"
|
||||
value={formValues.device}
|
||||
onChange={handleChange}
|
||||
inputProps={{ readOnly: !canEdit }}
|
||||
>
|
||||
{DeviceTypes.map((device) => (
|
||||
<MenuItem key={device.id} value={device.id}>
|
||||
|
|
@ -372,6 +382,8 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
</FormControl>
|
||||
</div>
|
||||
|
||||
{(canEdit || isPartner) && (
|
||||
<>
|
||||
<div>
|
||||
<TextField
|
||||
label={
|
||||
|
|
@ -385,6 +397,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
inputProps={{ readOnly: !canEdit }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -401,6 +414,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
inputProps={{ readOnly: !canEdit }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -417,6 +431,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
inputProps={{ readOnly: !canEdit }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -433,6 +448,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
inputProps={{ readOnly: !canEdit }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -450,7 +466,8 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
onChange={handleBatteryNumberChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
placeholder="Enter number of batteries"
|
||||
placeholder={canEdit ? 'Enter number of batteries' : ''}
|
||||
inputProps={{ readOnly: !canEdit }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -468,10 +485,13 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
inputRef={(el) => (batterySnRefs.current[index] = el)}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
placeholder="Scan or enter serial number"
|
||||
placeholder={canEdit ? 'Scan or enter serial number' : ''}
|
||||
inputProps={{ readOnly: !canEdit }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
|
|
@ -486,10 +506,11 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
inputProps={{ readOnly: !canEdit }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{currentUser.userType == UserType.admin && (
|
||||
{canEdit && (
|
||||
<>
|
||||
<div>
|
||||
<TextField
|
||||
|
|
@ -533,6 +554,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
marginTop: 10
|
||||
}}
|
||||
>
|
||||
{canEdit && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSubmit}
|
||||
|
|
@ -546,8 +568,9 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
defaultMessage="Apply Changes"
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{currentUser.userType == UserType.admin && (
|
||||
{canEdit && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleDelete}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@ import {
|
|||
IconButton,
|
||||
InputLabel,
|
||||
ListItem,
|
||||
ListSubheader,
|
||||
MenuItem,
|
||||
Modal,
|
||||
Select,
|
||||
Typography,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import { TokenContext } from 'src/contexts/tokenContext';
|
||||
|
|
@ -26,6 +28,7 @@ import ListItemAvatar from '@mui/material/ListItemAvatar';
|
|||
import Avatar from '@mui/material/Avatar';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import PersonIcon from '@mui/icons-material/Person';
|
||||
import FolderIcon from '@mui/icons-material/Folder';
|
||||
import Button from '@mui/material/Button';
|
||||
import { Close as CloseIcon } from '@mui/icons-material';
|
||||
import { AccessContext } from 'src/contexts/AccessContextProvider';
|
||||
|
|
@ -52,22 +55,24 @@ function UserAccess(props: UserAccessProps) {
|
|||
const tokencontext = useContext(TokenContext);
|
||||
const { removeToken } = tokencontext;
|
||||
const context = useContext(UserContext);
|
||||
const { currentUser, setUser } = context;
|
||||
const { currentUser } = context;
|
||||
const [openFolder, setOpenFolder] = useState(false);
|
||||
const [openInstallation, setOpenInstallation] = useState(false);
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
|
||||
const [selectedFolderNames, setSelectedFolderNames] = useState<string[]>([]);
|
||||
const [selectedInstallationNames, setSelectedInstallationNames] = useState<
|
||||
string[]
|
||||
>([]);
|
||||
const [selectedInstallationNames, setSelectedInstallationNames] = useState<string[]>([]);
|
||||
|
||||
// Available choices for grant modal
|
||||
const [availableFolders, setAvailableFolders] = useState<I_Folder[]>([]);
|
||||
const [availableInstallations, setAvailableInstallations] = useState<I_Installation[]>([]);
|
||||
|
||||
// Direct grants for this user
|
||||
const [directFolders, setDirectFolders] = useState<{ id: number; name: string }[]>([]);
|
||||
const [directInstallations, setDirectInstallations] = useState<{ id: number; name: string }[]>([]);
|
||||
|
||||
const [folders, setFolders] = useState<I_Folder[]>([]);
|
||||
const [installations, setInstallations] = useState<I_Installation[]>([]);
|
||||
const accessContext = useContext(AccessContext);
|
||||
const {
|
||||
fetchInstallationsForUser,
|
||||
accessibleInstallationsForUser,
|
||||
error,
|
||||
setError,
|
||||
updated,
|
||||
|
|
@ -75,130 +80,118 @@ function UserAccess(props: UserAccessProps) {
|
|||
updatedmessage,
|
||||
errormessage,
|
||||
setErrorMessage,
|
||||
setUpdatedMessage,
|
||||
RevokeAccessFromResource
|
||||
setUpdatedMessage
|
||||
} = accessContext;
|
||||
|
||||
const fetchFolders = useCallback(async () => {
|
||||
const fetchDirectGrants = useCallback(async () => {
|
||||
try {
|
||||
const [foldersRes, installationsRes] = await Promise.all([
|
||||
axiosConfig.get(`/GetDirectFolderAccessForUser?userId=${props.current_user.id}`),
|
||||
axiosConfig.get(`/GetDirectInstallationAccessForUser?userId=${props.current_user.id}`)
|
||||
]);
|
||||
setDirectFolders(foldersRes.data);
|
||||
setDirectInstallations(installationsRes.data);
|
||||
} catch (err) {
|
||||
if (err.response && err.response.status === 401) removeToken();
|
||||
}
|
||||
}, [props.current_user.id]);
|
||||
|
||||
const fetchAvailableFolders = useCallback(async () => {
|
||||
return axiosConfig
|
||||
.get('/GetAllFolders')
|
||||
.then((res) => {
|
||||
setFolders(res.data);
|
||||
})
|
||||
.then((res) => setAvailableFolders(res.data))
|
||||
.catch((err) => {
|
||||
if (err.response && err.response.status == 401) {
|
||||
removeToken();
|
||||
}
|
||||
if (err.response && err.response.status == 401) removeToken();
|
||||
});
|
||||
}, [setFolders]);
|
||||
}, []);
|
||||
|
||||
const fetchInstallations = useCallback(async () => {
|
||||
const fetchAvailableInstallations = useCallback(async () => {
|
||||
try {
|
||||
// fetch product 0
|
||||
const res0 = await axiosConfig.get(
|
||||
`/GetAllInstallationsFromProduct?product=0`
|
||||
);
|
||||
const installations0 = res0.data;
|
||||
|
||||
// fetch product 1
|
||||
const res1 = await axiosConfig.get(
|
||||
`/GetAllInstallationsFromProduct?product=3`
|
||||
);
|
||||
const installations1 = res1.data;
|
||||
|
||||
// aggregate
|
||||
const combined = [...installations0, ...installations1];
|
||||
|
||||
// update
|
||||
setInstallations(combined);
|
||||
const [res0, res1, res2, res3] = await Promise.all([
|
||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=0`),
|
||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=1`),
|
||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=2`),
|
||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=3`)
|
||||
]);
|
||||
setAvailableInstallations([...res0.data, ...res1.data, ...res2.data, ...res3.data]);
|
||||
} catch (err) {
|
||||
if (err.response && err.response.status === 401) {
|
||||
removeToken();
|
||||
if (err.response && err.response.status === 401) removeToken();
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
}, [setInstallations]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchInstallationsForUser(props.current_user.id);
|
||||
fetchDirectGrants();
|
||||
}, [props.current_user]);
|
||||
|
||||
const handleGrantAccess = () => {
|
||||
fetchFolders();
|
||||
fetchInstallations();
|
||||
fetchAvailableFolders();
|
||||
fetchAvailableInstallations();
|
||||
setOpenModal(true);
|
||||
setSelectedFolderNames([]);
|
||||
setSelectedInstallationNames([]);
|
||||
};
|
||||
|
||||
const handleFolderChange = (event) => {
|
||||
setSelectedFolderNames(event.target.value);
|
||||
const handleRevokeFolder = async (folderId: number, folderName: string) => {
|
||||
axiosConfig
|
||||
.post(`/RevokeUserAccessToFolder?UserId=${props.current_user.id}&FolderId=${folderId}`)
|
||||
.then(() => {
|
||||
setUpdatedMessage(intl.formatMessage({ id: 'revokedAccessFromUser' }) + ' ' + props.current_user.name);
|
||||
setUpdated(true);
|
||||
setTimeout(() => setUpdated(false), 3000);
|
||||
fetchDirectGrants();
|
||||
})
|
||||
.catch(() => {
|
||||
setErrorMessage(intl.formatMessage({ id: 'unableToRevokeAccess' }));
|
||||
setError(true);
|
||||
});
|
||||
};
|
||||
|
||||
const handleInstallationChange = (event) => {
|
||||
setSelectedInstallationNames(event.target.value);
|
||||
};
|
||||
const handleOpenFolder = () => {
|
||||
setOpenFolder(true);
|
||||
};
|
||||
|
||||
const handleCloseFolder = () => {
|
||||
setOpenFolder(false);
|
||||
};
|
||||
const handleCancel = () => {
|
||||
setOpenModal(false);
|
||||
};
|
||||
const handleOpenInstallation = () => {
|
||||
setOpenInstallation(true);
|
||||
};
|
||||
|
||||
const handleCloseInstallation = () => {
|
||||
setOpenInstallation(false);
|
||||
const handleRevokeInstallation = async (installationId: number) => {
|
||||
axiosConfig
|
||||
.post(`/RevokeUserAccessToInstallation?UserId=${props.current_user.id}&InstallationId=${installationId}`)
|
||||
.then(() => {
|
||||
setUpdatedMessage(intl.formatMessage({ id: 'revokedAccessFromUser' }) + ' ' + props.current_user.name);
|
||||
setUpdated(true);
|
||||
setTimeout(() => setUpdated(false), 3000);
|
||||
fetchDirectGrants();
|
||||
})
|
||||
.catch(() => {
|
||||
setErrorMessage(intl.formatMessage({ id: 'unableToRevokeAccess' }));
|
||||
setError(true);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
for (const folderName of selectedFolderNames) {
|
||||
const folder = folders.find((folder) => folder.name === folderName);
|
||||
|
||||
const folder = availableFolders.find((f) => f.name === folderName);
|
||||
await axiosConfig
|
||||
.post(
|
||||
`/GrantUserAccessToFolder?UserId=${props.current_user.id}&FolderId=${folder.id}`
|
||||
)
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
.post(`/GrantUserAccessToFolder?UserId=${props.current_user.id}&FolderId=${folder.id}`)
|
||||
.then(() => {
|
||||
setUpdatedMessage(intl.formatMessage({ id: 'grantedAccessToUser' }, { name: props.current_user.name }));
|
||||
setUpdated(true);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(() => {
|
||||
setErrorMessage(intl.formatMessage({ id: 'errorOccured' }));
|
||||
setError(true);
|
||||
});
|
||||
}
|
||||
|
||||
for (const installationName of selectedInstallationNames) {
|
||||
const installation = installations.find(
|
||||
(installation) => installation.name === installationName
|
||||
);
|
||||
|
||||
const installation = availableInstallations.find((i) => i.name === installationName);
|
||||
await axiosConfig
|
||||
.post(
|
||||
`/GrantUserAccessToInstallation?UserId=${props.current_user.id}&InstallationId=${installation.id}`
|
||||
)
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
.post(`/GrantUserAccessToInstallation?UserId=${props.current_user.id}&InstallationId=${installation.id}`)
|
||||
.then(() => {
|
||||
setUpdatedMessage(intl.formatMessage({ id: 'grantedAccessToUser' }, { name: props.current_user.name }));
|
||||
setUpdated(true);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(() => {
|
||||
setErrorMessage(intl.formatMessage({ id: 'errorOccured' }));
|
||||
setError(true);
|
||||
});
|
||||
}
|
||||
|
||||
setOpenModal(false);
|
||||
fetchInstallationsForUser(props.current_user.id);
|
||||
fetchDirectGrants();
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -206,51 +199,25 @@ function UserAccess(props: UserAccessProps) {
|
|||
<Grid container>
|
||||
<Grid item xs={12} md={12}>
|
||||
{updated && (
|
||||
<Alert
|
||||
severity="success"
|
||||
sx={{
|
||||
mt: 1
|
||||
}}
|
||||
>
|
||||
<Alert severity="success" sx={{ mt: 1 }}>
|
||||
{updatedmessage}
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => setUpdated(false)}
|
||||
>
|
||||
<IconButton color="inherit" size="small" onClick={() => setUpdated(false)}>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<Alert
|
||||
severity="error"
|
||||
sx={{
|
||||
marginTop: '20px',
|
||||
marginBottom: '20px'
|
||||
}}
|
||||
>
|
||||
<Alert severity="error" sx={{ marginTop: '20px', marginBottom: '20px' }}>
|
||||
{errormessage}
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => setError(false)}
|
||||
sx={{
|
||||
marginLeft: '10px'
|
||||
}}
|
||||
>
|
||||
<IconButton color="inherit" size="small" onClick={() => setError(false)} sx={{ marginLeft: '10px' }}>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Modal
|
||||
open={openModal}
|
||||
onClose={() => {}}
|
||||
aria-labelledby="error-modal"
|
||||
aria-describedby="error-modal-description"
|
||||
>
|
||||
{/* Grant Access Modal */}
|
||||
<Modal open={openModal} onClose={() => {}} aria-labelledby="grant-modal">
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
|
|
@ -264,59 +231,29 @@ function UserAccess(props: UserAccessProps) {
|
|||
p: 4
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{
|
||||
textAlign: 'center'
|
||||
}}
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
>
|
||||
<Box component="form" sx={{ textAlign: 'center' }} noValidate autoComplete="off">
|
||||
<div>
|
||||
<FormControl fullWidth sx={{ marginTop: 1, width: 390 }}>
|
||||
<InputLabel
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="grantAccessToFolders"
|
||||
defaultMessage="Grant access to folders"
|
||||
/>
|
||||
<InputLabel sx={{ fontSize: 14, backgroundColor: 'white' }}>
|
||||
<FormattedMessage id="grantAccessToFolders" defaultMessage="Grant access to folders" />
|
||||
</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={selectedFolderNames}
|
||||
onChange={handleFolderChange}
|
||||
onChange={(e) => setSelectedFolderNames(e.target.value as string[])}
|
||||
open={openFolder}
|
||||
onClose={handleCloseFolder}
|
||||
onOpen={handleOpenFolder}
|
||||
onClose={() => setOpenFolder(false)}
|
||||
onOpen={() => setOpenFolder(true)}
|
||||
renderValue={(selected) => (
|
||||
<div>
|
||||
{selected.map((folder) => (
|
||||
<span key={folder}>{folder}, </span>
|
||||
))}
|
||||
</div>
|
||||
<div>{selected.map((f) => <span key={f}>{f}, </span>)}</div>
|
||||
)}
|
||||
>
|
||||
{folders.map((folder) => (
|
||||
<MenuItem key={folder.id} value={folder.name}>
|
||||
{folder.name}
|
||||
</MenuItem>
|
||||
{availableFolders.map((folder) => (
|
||||
<MenuItem key={folder.id} value={folder.name}>{folder.name}</MenuItem>
|
||||
))}
|
||||
<Button
|
||||
sx={{
|
||||
marginLeft: '150px',
|
||||
marginTop: '10px',
|
||||
backgroundColor: theme.colors.primary.main,
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.colors.primary.dark
|
||||
},
|
||||
padding: '6px 8px'
|
||||
}}
|
||||
onClick={handleCloseFolder}
|
||||
sx={{ marginLeft: '150px', marginTop: '10px', backgroundColor: theme.colors.primary.main, color: 'white', '&:hover': { backgroundColor: theme.colors.primary.dark }, padding: '6px 8px' }}
|
||||
onClick={() => setOpenFolder(false)}
|
||||
>
|
||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||
</Button>
|
||||
|
|
@ -326,52 +263,26 @@ function UserAccess(props: UserAccessProps) {
|
|||
|
||||
<div>
|
||||
<FormControl fullWidth sx={{ marginTop: 2, width: 390 }}>
|
||||
<InputLabel
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="grantAccessToInstallations"
|
||||
defaultMessage="Grant access to installations"
|
||||
/>
|
||||
<InputLabel sx={{ fontSize: 14, backgroundColor: 'white' }}>
|
||||
<FormattedMessage id="grantAccessToInstallations" defaultMessage="Grant access to installations" />
|
||||
</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={selectedInstallationNames}
|
||||
onChange={handleInstallationChange}
|
||||
onChange={(e) => setSelectedInstallationNames(e.target.value as string[])}
|
||||
open={openInstallation}
|
||||
onClose={handleCloseInstallation}
|
||||
onOpen={handleOpenInstallation}
|
||||
onClose={() => setOpenInstallation(false)}
|
||||
onOpen={() => setOpenInstallation(true)}
|
||||
renderValue={(selected) => (
|
||||
<div>
|
||||
{selected.map((installation) => (
|
||||
<span key={installation}>{installation}, </span>
|
||||
))}
|
||||
</div>
|
||||
<div>{selected.map((i) => <span key={i}>{i}, </span>)}</div>
|
||||
)}
|
||||
>
|
||||
{installations.map((installation) => (
|
||||
<MenuItem
|
||||
key={installation.id}
|
||||
value={installation.name}
|
||||
>
|
||||
{installation.name}
|
||||
</MenuItem>
|
||||
{availableInstallations.map((installation) => (
|
||||
<MenuItem key={installation.id} value={installation.name}>{installation.name}</MenuItem>
|
||||
))}
|
||||
<Button
|
||||
sx={{
|
||||
marginLeft: '150px',
|
||||
marginTop: '10px',
|
||||
backgroundColor: theme.colors.primary.main,
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.colors.primary.dark
|
||||
},
|
||||
padding: '6px 8px'
|
||||
}}
|
||||
onClick={handleCloseInstallation}
|
||||
sx={{ marginLeft: '150px', marginTop: '10px', backgroundColor: theme.colors.primary.main, color: 'white', '&:hover': { backgroundColor: theme.colors.primary.dark }, padding: '6px 8px' }}
|
||||
onClick={() => setOpenInstallation(false)}
|
||||
>
|
||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||
</Button>
|
||||
|
|
@ -380,32 +291,15 @@ function UserAccess(props: UserAccessProps) {
|
|||
</div>
|
||||
|
||||
<Button
|
||||
sx={{
|
||||
marginTop: '20px',
|
||||
backgroundColor: theme.colors.primary.main,
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.colors.primary.dark
|
||||
},
|
||||
padding: '6px 8px'
|
||||
}}
|
||||
sx={{ marginTop: '20px', backgroundColor: theme.colors.primary.main, color: 'white', '&:hover': { backgroundColor: theme.colors.primary.dark }, padding: '6px 8px' }}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleCancel}
|
||||
sx={{
|
||||
marginTop: '20px',
|
||||
marginLeft: '10px',
|
||||
backgroundColor: theme.colors.primary.main,
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.colors.primary.dark
|
||||
},
|
||||
padding: '6px 8px'
|
||||
}}
|
||||
onClick={() => setOpenModal(false)}
|
||||
sx={{ marginTop: '20px', marginLeft: '10px', backgroundColor: theme.colors.primary.main, color: 'white', '&:hover': { backgroundColor: theme.colors.primary.dark }, padding: '6px 8px' }}
|
||||
>
|
||||
<FormattedMessage id="cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
|
|
@ -416,43 +310,63 @@ function UserAccess(props: UserAccessProps) {
|
|||
<Button
|
||||
variant="contained"
|
||||
onClick={handleGrantAccess}
|
||||
sx={{
|
||||
marginTop: '20px',
|
||||
marginBottom: '20px',
|
||||
backgroundColor: '#ffc04d',
|
||||
color: '#000000',
|
||||
'&:hover': { bgcolor: '#f7b34d' }
|
||||
}}
|
||||
sx={{ marginTop: '20px', marginBottom: '20px', backgroundColor: '#ffc04d', color: '#000000', '&:hover': { bgcolor: '#f7b34d' } }}
|
||||
>
|
||||
<FormattedMessage id="grantAccess" defaultMessage="Grant Access" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12}>
|
||||
{accessibleInstallationsForUser.map((installation, index) => {
|
||||
const isLast = index === accessibleInstallationsForUser.length - 1;
|
||||
|
||||
{/* Folder Access Section */}
|
||||
<Grid item xs={12} md={12}>
|
||||
<Typography variant="subtitle2" sx={{ mt: 1, mb: 0.5, color: 'text.secondary', fontWeight: 600 }}>
|
||||
<FormattedMessage id="folderAccess" defaultMessage="Folder Access" />
|
||||
</Typography>
|
||||
{directFolders.map((folder, index) => {
|
||||
const isLast = index === directFolders.length - 1;
|
||||
return (
|
||||
<Fragment key={installation.name}>
|
||||
<Fragment key={folder.id}>
|
||||
<ListItem
|
||||
sx={{
|
||||
mb: isLast ? 4 : 0 // Apply margin-bottom to the last item only
|
||||
}}
|
||||
sx={{ mb: isLast ? 1 : 0 }}
|
||||
secondaryAction={
|
||||
currentUser.userType === UserType.admin && (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
RevokeAccessFromResource(
|
||||
'ToInstallation',
|
||||
props.current_user.id,
|
||||
'InstallationId',
|
||||
installation.id,
|
||||
props.current_user.name
|
||||
);
|
||||
|
||||
fetchInstallationsForUser(props.current_user.id);
|
||||
}}
|
||||
edge="end"
|
||||
<IconButton onClick={() => handleRevokeFolder(folder.id, folder.name)} edge="end">
|
||||
<PersonRemoveIcon />
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<FolderIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={folder.name} />
|
||||
</ListItem>
|
||||
<Divider />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
{directFolders.length === 0 && (
|
||||
<Alert severity="info" sx={{ mb: 1 }}>
|
||||
<FormattedMessage id="noDirectFolderAccess" defaultMessage="No folder access grants" />
|
||||
</Alert>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
{/* Direct Installation Access Section */}
|
||||
<Grid item xs={12} md={12}>
|
||||
<Typography variant="subtitle2" sx={{ mt: 2, mb: 0.5, color: 'text.secondary', fontWeight: 600 }}>
|
||||
<FormattedMessage id="directInstallationAccess" defaultMessage="Direct Installation Access" />
|
||||
</Typography>
|
||||
{directInstallations.map((installation, index) => {
|
||||
const isLast = index === directInstallations.length - 1;
|
||||
return (
|
||||
<Fragment key={installation.id}>
|
||||
<ListItem
|
||||
sx={{ mb: isLast ? 4 : 0 }}
|
||||
secondaryAction={
|
||||
currentUser.userType === UserType.admin && (
|
||||
<IconButton onClick={() => handleRevokeInstallation(installation.id)} edge="end">
|
||||
<PersonRemoveIcon />
|
||||
</IconButton>
|
||||
)
|
||||
|
|
@ -469,22 +383,9 @@ function UserAccess(props: UserAccessProps) {
|
|||
</Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
{accessibleInstallationsForUser.length == 0 && (
|
||||
<Alert
|
||||
severity="error"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: '20px',
|
||||
marginBottom: '20px'
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="theUserDoesNOtHaveAccessToAnyInstallation"
|
||||
defaultMessage="The user does not have access to any installation "
|
||||
/>
|
||||
<IconButton color="inherit" size="small"></IconButton>
|
||||
{directInstallations.length === 0 && (
|
||||
<Alert severity="info" sx={{ mb: 4 }}>
|
||||
<FormattedMessage id="noDirectInstallationAccess" defaultMessage="No direct installation access grants" />
|
||||
</Alert>
|
||||
)}
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -474,6 +474,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
|||
}
|
||||
/>
|
||||
|
||||
{currentUser.userType !== UserType.client && (
|
||||
<Route
|
||||
path={routes.log}
|
||||
element={
|
||||
|
|
@ -484,6 +485,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
|||
></Log>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Route
|
||||
path={routes.live}
|
||||
|
|
@ -497,6 +499,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
|||
}
|
||||
/>
|
||||
|
||||
{currentUser.userType !== UserType.client && (
|
||||
<Route
|
||||
path={routes.batteryview + '/*'}
|
||||
element={
|
||||
|
|
@ -508,7 +511,8 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
|||
connected={connected}
|
||||
></BatteryViewSodioHome>
|
||||
}
|
||||
></Route>
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentUser.userType == UserType.admin && (
|
||||
<Route
|
||||
|
|
@ -559,7 +563,6 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
|||
}
|
||||
/>
|
||||
|
||||
{currentUser.userType == UserType.admin && (
|
||||
<Route
|
||||
path={routes.report}
|
||||
element={
|
||||
|
|
@ -568,7 +571,6 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
|||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Route
|
||||
path={'*'}
|
||||
|
|
|
|||
|
|
@ -118,7 +118,6 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
value: 'log',
|
||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||
},
|
||||
|
||||
{
|
||||
value: 'manage',
|
||||
label: (
|
||||
|
|
@ -128,14 +127,12 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
/>
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
value: 'information',
|
||||
label: (
|
||||
<FormattedMessage id="information" defaultMessage="Information" />
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
value: 'configuration',
|
||||
label: (
|
||||
|
|
@ -164,6 +161,45 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
)
|
||||
}
|
||||
]
|
||||
: currentUser.userType == UserType.partner
|
||||
? [
|
||||
{
|
||||
value: 'live',
|
||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||
},
|
||||
{
|
||||
value: 'overview',
|
||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||
},
|
||||
{
|
||||
value: 'batteryview',
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="batteryview"
|
||||
defaultMessage="Battery View"
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'log',
|
||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||
},
|
||||
{
|
||||
value: 'information',
|
||||
label: (
|
||||
<FormattedMessage id="information" defaultMessage="Information" />
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'report',
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="report"
|
||||
defaultMessage="Report"
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
value: 'live',
|
||||
|
|
@ -178,14 +214,24 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
label: (
|
||||
<FormattedMessage id="information" defaultMessage="Information" />
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'report',
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="report"
|
||||
defaultMessage="Report"
|
||||
/>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const tabs =
|
||||
const inInstallationView =
|
||||
currentTab != 'list' &&
|
||||
currentTab != 'tree' &&
|
||||
!location.pathname.includes('folder') &&
|
||||
currentUser.userType == UserType.admin
|
||||
!location.pathname.includes('folder');
|
||||
|
||||
const tabs = inInstallationView && currentUser.userType == UserType.admin
|
||||
? [
|
||||
{
|
||||
value: 'list',
|
||||
|
|
@ -216,7 +262,6 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
value: 'log',
|
||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||
},
|
||||
|
||||
{
|
||||
value: 'manage',
|
||||
label: (
|
||||
|
|
@ -226,7 +271,6 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
/>
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
value: 'information',
|
||||
label: (
|
||||
|
|
@ -261,10 +305,7 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
)
|
||||
}
|
||||
]
|
||||
: currentTab != 'list' &&
|
||||
currentTab != 'tree' &&
|
||||
!location.pathname.includes('folder') &&
|
||||
currentUser.userType == UserType.client
|
||||
: inInstallationView && currentUser.userType == UserType.partner
|
||||
? [
|
||||
{
|
||||
value: 'list',
|
||||
|
|
@ -282,12 +323,67 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
value: 'overview',
|
||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||
},
|
||||
|
||||
{
|
||||
value: 'batteryview',
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="batteryview"
|
||||
defaultMessage="Battery View"
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'log',
|
||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||
},
|
||||
{
|
||||
value: 'information',
|
||||
label: (
|
||||
<FormattedMessage id="information" defaultMessage="Information" />
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'report',
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="report"
|
||||
defaultMessage="Report"
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
: inInstallationView && currentUser.userType == UserType.client
|
||||
? [
|
||||
{
|
||||
value: 'list',
|
||||
icon: <ListIcon id="mode-toggle-button-list-icon" />
|
||||
},
|
||||
{
|
||||
value: 'tree',
|
||||
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
||||
},
|
||||
{
|
||||
value: 'live',
|
||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||
},
|
||||
{
|
||||
value: 'overview',
|
||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||
},
|
||||
{
|
||||
value: 'information',
|
||||
label: (
|
||||
<FormattedMessage id="information" defaultMessage="Information" />
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'report',
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id="report"
|
||||
defaultMessage="Report"
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
: [
|
||||
|
|
|
|||
|
|
@ -44,12 +44,12 @@ function Folder(props: singleFolderProps) {
|
|||
value: 'information',
|
||||
label: <FormattedMessage id="information" defaultMessage="Information" />
|
||||
},
|
||||
{
|
||||
...(currentUser.userType === UserType.admin ? [{
|
||||
value: 'manage',
|
||||
label: (
|
||||
<FormattedMessage id="manageAccess" defaultMessage="Manage Access" />
|
||||
)
|
||||
}
|
||||
}] : [])
|
||||
];
|
||||
|
||||
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue