Compare commits
No commits in common. "7476c939c3b363d4033288f57e7e8dacaec2793b" and "d464c9cd716444a25f004d90061057c05382ea93" have entirely different histories.
7476c939c3
...
d464c9cd71
|
|
@ -363,38 +363,6 @@ public class Controller : ControllerBase
|
||||||
return user.AccessibleInstallations().ToList();
|
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))]
|
[HttpGet(nameof(GetUsersWithInheritedAccessToInstallation))]
|
||||||
public ActionResult<IEnumerable<Object>> GetUsersWithInheritedAccessToInstallation(Int64 id, Token authToken)
|
public ActionResult<IEnumerable<Object>> GetUsersWithInheritedAccessToInstallation(Int64 id, Token authToken)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -376,7 +376,8 @@ public static class SessionMethods
|
||||||
&& sessionUser.HasAccessTo(originalUser)
|
&& sessionUser.HasAccessTo(originalUser)
|
||||||
&& editedUser
|
&& editedUser
|
||||||
.WithParentOf(originalUser) // prevent moving
|
.WithParentOf(originalUser) // prevent moving
|
||||||
.WithPasswordOf(originalUser)
|
.WithNameOf(originalUser)
|
||||||
|
.WithPasswordOf(originalUser)
|
||||||
.Apply(Db.Update);
|
.Apply(Db.Update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -472,36 +473,24 @@ public static class SessionMethods
|
||||||
public static Boolean RevokeUserAccessTo(this Session? session, User? user, Installation? installation)
|
public static Boolean RevokeUserAccessTo(this Session? session, User? user, Installation? installation)
|
||||||
{
|
{
|
||||||
var sessionUser = session?.User;
|
var sessionUser = session?.User;
|
||||||
|
|
||||||
if (sessionUser is null || installation is null || user is null)
|
return sessionUser is not null
|
||||||
return false;
|
&& installation is not null
|
||||||
|
&& user is not null
|
||||||
if (!(user.IsDescendantOf(sessionUser) || sessionUser.UserType == 2))
|
&& user.IsDescendantOf(sessionUser)
|
||||||
return false;
|
&& sessionUser.HasAccessTo(installation)
|
||||||
|
&& user.HasAccessTo(installation)
|
||||||
if (!sessionUser.HasAccessTo(installation) || !user.HasAccessTo(installation))
|
&& Db.InstallationAccess.Delete(a => a.UserId == user.Id && a.InstallationId == installation.Id) > 0;
|
||||||
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)
|
public static Boolean RevokeUserAccessTo(this Session? session, User? user, Folder? folder)
|
||||||
{
|
{
|
||||||
var sessionUser = session?.User;
|
var sessionUser = session?.User;
|
||||||
|
|
||||||
return sessionUser is not null
|
return sessionUser is not null
|
||||||
&& folder is not null
|
&& folder is not null
|
||||||
&& user is not null
|
&& user is not null
|
||||||
&& (user.IsDescendantOf(sessionUser) || sessionUser.UserType == 2)
|
&& user.IsDescendantOf(sessionUser)
|
||||||
&& sessionUser.HasAccessTo(folder)
|
&& sessionUser.HasAccessTo(folder)
|
||||||
&& user.HasAccessTo(folder)
|
&& user.HasAccessTo(folder)
|
||||||
&& Db.FolderAccess.Delete(a => a.UserId == user.Id && a.FolderId == folder.Id) > 0;
|
&& Db.FolderAccess.Delete(a => a.UserId == user.Id && a.FolderId == folder.Id) > 0;
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,10 @@ public static partial class Db
|
||||||
var originalUser = GetUserById(user.Id);
|
var originalUser = GetUserById(user.Id);
|
||||||
if (originalUser is null) return false;
|
if (originalUser is null) return false;
|
||||||
|
|
||||||
// ParentId must not be modified via this method
|
// these columns must not be modified!
|
||||||
user.ParentId = originalUser.ParentId;
|
user.ParentId = originalUser.ParentId;
|
||||||
|
user.Name = originalUser.Name;
|
||||||
|
|
||||||
return Update(obj: user);
|
return Update(obj: user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -175,9 +175,6 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const canEdit = currentUser.userType === UserType.admin;
|
|
||||||
const isPartner = currentUser.userType === UserType.partner;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{openModalDeleteInstallation && (
|
{openModalDeleteInstallation && (
|
||||||
|
|
@ -279,7 +276,6 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
inputProps={{ readOnly: !canEdit }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -292,9 +288,8 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
required={canEdit}
|
required
|
||||||
error={canEdit && formValues.region === ''}
|
error={formValues.region === ''}
|
||||||
inputProps={{ readOnly: !canEdit }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -310,9 +305,8 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
required={canEdit}
|
required
|
||||||
error={canEdit && formValues.location === ''}
|
error={formValues.location === ''}
|
||||||
inputProps={{ readOnly: !canEdit }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -325,26 +319,23 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
required={canEdit}
|
required
|
||||||
error={canEdit && formValues.country === ''}
|
error={formValues.country === ''}
|
||||||
inputProps={{ readOnly: !canEdit }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{canEdit && (
|
<div>
|
||||||
<div>
|
<TextField
|
||||||
<TextField
|
label={
|
||||||
label={
|
<FormattedMessage id="vpnip" defaultMessage="VPN IP" />
|
||||||
<FormattedMessage id="vpnip" defaultMessage="VPN IP" />
|
}
|
||||||
}
|
name="vpnIp"
|
||||||
name="vpnIp"
|
value={formValues.vpnIp}
|
||||||
value={formValues.vpnIp}
|
onChange={handleChange}
|
||||||
onChange={handleChange}
|
variant="outlined"
|
||||||
variant="outlined"
|
fullWidth
|
||||||
fullWidth
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<FormControl
|
<FormControl
|
||||||
|
|
@ -371,7 +362,6 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
name="device"
|
name="device"
|
||||||
value={formValues.device}
|
value={formValues.device}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
inputProps={{ readOnly: !canEdit }}
|
|
||||||
>
|
>
|
||||||
{DeviceTypes.map((device) => (
|
{DeviceTypes.map((device) => (
|
||||||
<MenuItem key={device.id} value={device.id}>
|
<MenuItem key={device.id} value={device.id}>
|
||||||
|
|
@ -382,116 +372,106 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(canEdit || isPartner) && (
|
<div>
|
||||||
<>
|
<TextField
|
||||||
<div>
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="installationSerialNumber"
|
||||||
|
defaultMessage="Installation Serial Number"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="serialNumber"
|
||||||
|
value={formValues.serialNumber}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="inverterSN"
|
||||||
|
defaultMessage="Inverter Serial Number"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="inverterSN"
|
||||||
|
value={formValues.inverterSN}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="dataloggerSN"
|
||||||
|
defaultMessage="Datalogger Serial Number"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="dataloggerSN"
|
||||||
|
value={formValues.dataloggerSN}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="batteryClusterNumber"
|
||||||
|
defaultMessage="Battery Cluster Number"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="batteryClusterNumber"
|
||||||
|
value={formValues.batteryClusterNumber}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="batteryNumber"
|
||||||
|
defaultMessage="Battery Number"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="batteryNumber"
|
||||||
|
type="text"
|
||||||
|
value={batteryNumber === 0 ? '' : batteryNumber}
|
||||||
|
onChange={handleBatteryNumberChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
placeholder="Enter number of batteries"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{batteryNumber > 0 &&
|
||||||
|
batterySerialNumbers.map((serialNumber, index) => (
|
||||||
|
<div key={index}>
|
||||||
<TextField
|
<TextField
|
||||||
label={
|
label={`Battery Pack ${index + 1}`}
|
||||||
<FormattedMessage
|
name={`batterySN${index + 1}`}
|
||||||
id="installationSerialNumber"
|
value={serialNumber}
|
||||||
defaultMessage="Installation Serial Number"
|
onChange={(e) =>
|
||||||
/>
|
handleBatterySerialNumberChange(index, e.target.value)
|
||||||
}
|
}
|
||||||
name="serialNumber"
|
onKeyDown={(e) => handleBatterySnKeyDown(e, index)}
|
||||||
value={formValues.serialNumber}
|
inputRef={(el) => (batterySnRefs.current[index] = el)}
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
inputProps={{ readOnly: !canEdit }}
|
placeholder="Scan or enter serial number"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="inverterSN"
|
|
||||||
defaultMessage="Inverter Serial Number"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="inverterSN"
|
|
||||||
value={formValues.inverterSN}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
inputProps={{ readOnly: !canEdit }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="dataloggerSN"
|
|
||||||
defaultMessage="Datalogger Serial Number"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="dataloggerSN"
|
|
||||||
value={formValues.dataloggerSN}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
inputProps={{ readOnly: !canEdit }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="batteryClusterNumber"
|
|
||||||
defaultMessage="Battery Cluster Number"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="batteryClusterNumber"
|
|
||||||
value={formValues.batteryClusterNumber}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
inputProps={{ readOnly: !canEdit }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="batteryNumber"
|
|
||||||
defaultMessage="Battery Number"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="batteryNumber"
|
|
||||||
type="text"
|
|
||||||
value={batteryNumber === 0 ? '' : batteryNumber}
|
|
||||||
onChange={handleBatteryNumberChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
placeholder={canEdit ? 'Enter number of batteries' : ''}
|
|
||||||
inputProps={{ readOnly: !canEdit }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{batteryNumber > 0 &&
|
|
||||||
batterySerialNumbers.map((serialNumber, index) => (
|
|
||||||
<div key={index}>
|
|
||||||
<TextField
|
|
||||||
label={`Battery Pack ${index + 1}`}
|
|
||||||
name={`batterySN${index + 1}`}
|
|
||||||
value={serialNumber}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleBatterySerialNumberChange(index, e.target.value)
|
|
||||||
}
|
|
||||||
onKeyDown={(e) => handleBatterySnKeyDown(e, index)}
|
|
||||||
inputRef={(el) => (batterySnRefs.current[index] = el)}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
placeholder={canEdit ? 'Scan or enter serial number' : ''}
|
|
||||||
inputProps={{ readOnly: !canEdit }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -506,11 +486,10 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
inputProps={{ readOnly: !canEdit }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{canEdit && (
|
{currentUser.userType == UserType.admin && (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -554,23 +533,21 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
||||||
marginTop: 10
|
marginTop: 10
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{canEdit && (
|
<Button
|
||||||
<Button
|
variant="contained"
|
||||||
variant="contained"
|
onClick={handleSubmit}
|
||||||
onClick={handleSubmit}
|
sx={{
|
||||||
sx={{
|
marginLeft: '10px'
|
||||||
marginLeft: '10px'
|
}}
|
||||||
}}
|
disabled={!areRequiredFieldsFilled()}
|
||||||
disabled={!areRequiredFieldsFilled()}
|
>
|
||||||
>
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id="applyChanges"
|
||||||
id="applyChanges"
|
defaultMessage="Apply Changes"
|
||||||
defaultMessage="Apply Changes"
|
/>
|
||||||
/>
|
</Button>
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{canEdit && (
|
{currentUser.userType == UserType.admin && (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,9 @@ import {
|
||||||
IconButton,
|
IconButton,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListSubheader,
|
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Modal,
|
Modal,
|
||||||
Select,
|
Select,
|
||||||
Typography,
|
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
|
|
@ -28,7 +26,6 @@ import ListItemAvatar from '@mui/material/ListItemAvatar';
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
import ListItemText from '@mui/material/ListItemText';
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
import PersonIcon from '@mui/icons-material/Person';
|
import PersonIcon from '@mui/icons-material/Person';
|
||||||
import FolderIcon from '@mui/icons-material/Folder';
|
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { Close as CloseIcon } from '@mui/icons-material';
|
import { Close as CloseIcon } from '@mui/icons-material';
|
||||||
import { AccessContext } from 'src/contexts/AccessContextProvider';
|
import { AccessContext } from 'src/contexts/AccessContextProvider';
|
||||||
|
|
@ -55,24 +52,22 @@ function UserAccess(props: UserAccessProps) {
|
||||||
const tokencontext = useContext(TokenContext);
|
const tokencontext = useContext(TokenContext);
|
||||||
const { removeToken } = tokencontext;
|
const { removeToken } = tokencontext;
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser } = context;
|
const { currentUser, setUser } = context;
|
||||||
const [openFolder, setOpenFolder] = useState(false);
|
const [openFolder, setOpenFolder] = useState(false);
|
||||||
const [openInstallation, setOpenInstallation] = useState(false);
|
const [openInstallation, setOpenInstallation] = useState(false);
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
|
|
||||||
const [selectedFolderNames, setSelectedFolderNames] = useState<string[]>([]);
|
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 accessContext = useContext(AccessContext);
|
||||||
const {
|
const {
|
||||||
|
fetchInstallationsForUser,
|
||||||
|
accessibleInstallationsForUser,
|
||||||
error,
|
error,
|
||||||
setError,
|
setError,
|
||||||
updated,
|
updated,
|
||||||
|
|
@ -80,118 +75,130 @@ function UserAccess(props: UserAccessProps) {
|
||||||
updatedmessage,
|
updatedmessage,
|
||||||
errormessage,
|
errormessage,
|
||||||
setErrorMessage,
|
setErrorMessage,
|
||||||
setUpdatedMessage
|
setUpdatedMessage,
|
||||||
|
RevokeAccessFromResource
|
||||||
} = accessContext;
|
} = accessContext;
|
||||||
|
|
||||||
const fetchDirectGrants = useCallback(async () => {
|
const fetchFolders = 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
|
return axiosConfig
|
||||||
.get('/GetAllFolders')
|
.get('/GetAllFolders')
|
||||||
.then((res) => setAvailableFolders(res.data))
|
.then((res) => {
|
||||||
|
setFolders(res.data);
|
||||||
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.response && err.response.status == 401) removeToken();
|
if (err.response && err.response.status == 401) {
|
||||||
|
removeToken();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, []);
|
}, [setFolders]);
|
||||||
|
|
||||||
const fetchAvailableInstallations = useCallback(async () => {
|
const fetchInstallations = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const [res0, res1, res2, res3] = await Promise.all([
|
// fetch product 0
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=0`),
|
const res0 = await axiosConfig.get(
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=1`),
|
`/GetAllInstallationsFromProduct?product=0`
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=2`),
|
);
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=3`)
|
const installations0 = res0.data;
|
||||||
]);
|
|
||||||
setAvailableInstallations([...res0.data, ...res1.data, ...res2.data, ...res3.data]);
|
// fetch product 1
|
||||||
|
const res1 = await axiosConfig.get(
|
||||||
|
`/GetAllInstallationsFromProduct?product=3`
|
||||||
|
);
|
||||||
|
const installations1 = res1.data;
|
||||||
|
|
||||||
|
// aggregate
|
||||||
|
const combined = [...installations0, ...installations1];
|
||||||
|
|
||||||
|
// update
|
||||||
|
setInstallations(combined);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.response && err.response.status === 401) removeToken();
|
if (err.response && err.response.status === 401) {
|
||||||
|
removeToken();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
}
|
}
|
||||||
}, []);
|
}, [setInstallations]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDirectGrants();
|
fetchInstallationsForUser(props.current_user.id);
|
||||||
}, [props.current_user]);
|
}, [props.current_user]);
|
||||||
|
|
||||||
const handleGrantAccess = () => {
|
const handleGrantAccess = () => {
|
||||||
fetchAvailableFolders();
|
fetchFolders();
|
||||||
fetchAvailableInstallations();
|
fetchInstallations();
|
||||||
setOpenModal(true);
|
setOpenModal(true);
|
||||||
setSelectedFolderNames([]);
|
setSelectedFolderNames([]);
|
||||||
setSelectedInstallationNames([]);
|
setSelectedInstallationNames([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRevokeFolder = async (folderId: number, folderName: string) => {
|
const handleFolderChange = (event) => {
|
||||||
axiosConfig
|
setSelectedFolderNames(event.target.value);
|
||||||
.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 handleRevokeInstallation = async (installationId: number) => {
|
const handleInstallationChange = (event) => {
|
||||||
axiosConfig
|
setSelectedInstallationNames(event.target.value);
|
||||||
.post(`/RevokeUserAccessToInstallation?UserId=${props.current_user.id}&InstallationId=${installationId}`)
|
};
|
||||||
.then(() => {
|
const handleOpenFolder = () => {
|
||||||
setUpdatedMessage(intl.formatMessage({ id: 'revokedAccessFromUser' }) + ' ' + props.current_user.name);
|
setOpenFolder(true);
|
||||||
setUpdated(true);
|
};
|
||||||
setTimeout(() => setUpdated(false), 3000);
|
|
||||||
fetchDirectGrants();
|
const handleCloseFolder = () => {
|
||||||
})
|
setOpenFolder(false);
|
||||||
.catch(() => {
|
};
|
||||||
setErrorMessage(intl.formatMessage({ id: 'unableToRevokeAccess' }));
|
const handleCancel = () => {
|
||||||
setError(true);
|
setOpenModal(false);
|
||||||
});
|
};
|
||||||
|
const handleOpenInstallation = () => {
|
||||||
|
setOpenInstallation(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseInstallation = () => {
|
||||||
|
setOpenInstallation(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
for (const folderName of selectedFolderNames) {
|
for (const folderName of selectedFolderNames) {
|
||||||
const folder = availableFolders.find((f) => f.name === folderName);
|
const folder = folders.find((folder) => folder.name === folderName);
|
||||||
|
|
||||||
await axiosConfig
|
await axiosConfig
|
||||||
.post(`/GrantUserAccessToFolder?UserId=${props.current_user.id}&FolderId=${folder.id}`)
|
.post(
|
||||||
.then(() => {
|
`/GrantUserAccessToFolder?UserId=${props.current_user.id}&FolderId=${folder.id}`
|
||||||
setUpdatedMessage(intl.formatMessage({ id: 'grantedAccessToUser' }, { name: props.current_user.name }));
|
)
|
||||||
setUpdated(true);
|
.then((response) => {
|
||||||
|
if (response) {
|
||||||
|
setUpdatedMessage(intl.formatMessage({ id: 'grantedAccessToUser' }, { name: props.current_user.name }));
|
||||||
|
setUpdated(true);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((err) => {
|
||||||
setErrorMessage(intl.formatMessage({ id: 'errorOccured' }));
|
setErrorMessage(intl.formatMessage({ id: 'errorOccured' }));
|
||||||
setError(true);
|
setError(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const installationName of selectedInstallationNames) {
|
for (const installationName of selectedInstallationNames) {
|
||||||
const installation = availableInstallations.find((i) => i.name === installationName);
|
const installation = installations.find(
|
||||||
|
(installation) => installation.name === installationName
|
||||||
|
);
|
||||||
|
|
||||||
await axiosConfig
|
await axiosConfig
|
||||||
.post(`/GrantUserAccessToInstallation?UserId=${props.current_user.id}&InstallationId=${installation.id}`)
|
.post(
|
||||||
.then(() => {
|
`/GrantUserAccessToInstallation?UserId=${props.current_user.id}&InstallationId=${installation.id}`
|
||||||
setUpdatedMessage(intl.formatMessage({ id: 'grantedAccessToUser' }, { name: props.current_user.name }));
|
)
|
||||||
setUpdated(true);
|
.then((response) => {
|
||||||
|
if (response) {
|
||||||
|
setUpdatedMessage(intl.formatMessage({ id: 'grantedAccessToUser' }, { name: props.current_user.name }));
|
||||||
|
setUpdated(true);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((err) => {
|
||||||
setErrorMessage(intl.formatMessage({ id: 'errorOccured' }));
|
setErrorMessage(intl.formatMessage({ id: 'errorOccured' }));
|
||||||
setError(true);
|
setError(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setOpenModal(false);
|
setOpenModal(false);
|
||||||
fetchDirectGrants();
|
fetchInstallationsForUser(props.current_user.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -199,25 +206,51 @@ function UserAccess(props: UserAccessProps) {
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={12} md={12}>
|
<Grid item xs={12} md={12}>
|
||||||
{updated && (
|
{updated && (
|
||||||
<Alert severity="success" sx={{ mt: 1 }}>
|
<Alert
|
||||||
|
severity="success"
|
||||||
|
sx={{
|
||||||
|
mt: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
{updatedmessage}
|
{updatedmessage}
|
||||||
<IconButton color="inherit" size="small" onClick={() => setUpdated(false)}>
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setUpdated(false)}
|
||||||
|
>
|
||||||
<CloseIcon fontSize="small" />
|
<CloseIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<Alert severity="error" sx={{ marginTop: '20px', marginBottom: '20px' }}>
|
<Alert
|
||||||
|
severity="error"
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginBottom: '20px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
{errormessage}
|
{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" />
|
<CloseIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Grant Access Modal */}
|
<Modal
|
||||||
<Modal open={openModal} onClose={() => {}} aria-labelledby="grant-modal">
|
open={openModal}
|
||||||
|
onClose={() => {}}
|
||||||
|
aria-labelledby="error-modal"
|
||||||
|
aria-describedby="error-modal-description"
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|
@ -231,29 +264,59 @@ function UserAccess(props: UserAccessProps) {
|
||||||
p: 4
|
p: 4
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box component="form" sx={{ textAlign: 'center' }} noValidate autoComplete="off">
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
noValidate
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<FormControl fullWidth sx={{ marginTop: 1, width: 390 }}>
|
<FormControl fullWidth sx={{ marginTop: 1, width: 390 }}>
|
||||||
<InputLabel sx={{ fontSize: 14, backgroundColor: 'white' }}>
|
<InputLabel
|
||||||
<FormattedMessage id="grantAccessToFolders" defaultMessage="Grant access to folders" />
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="grantAccessToFolders"
|
||||||
|
defaultMessage="Grant access to folders"
|
||||||
|
/>
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
value={selectedFolderNames}
|
value={selectedFolderNames}
|
||||||
onChange={(e) => setSelectedFolderNames(e.target.value as string[])}
|
onChange={handleFolderChange}
|
||||||
open={openFolder}
|
open={openFolder}
|
||||||
onClose={() => setOpenFolder(false)}
|
onClose={handleCloseFolder}
|
||||||
onOpen={() => setOpenFolder(true)}
|
onOpen={handleOpenFolder}
|
||||||
renderValue={(selected) => (
|
renderValue={(selected) => (
|
||||||
<div>{selected.map((f) => <span key={f}>{f}, </span>)}</div>
|
<div>
|
||||||
|
{selected.map((folder) => (
|
||||||
|
<span key={folder}>{folder}, </span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{availableFolders.map((folder) => (
|
{folders.map((folder) => (
|
||||||
<MenuItem key={folder.id} value={folder.name}>{folder.name}</MenuItem>
|
<MenuItem key={folder.id} value={folder.name}>
|
||||||
|
{folder.name}
|
||||||
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
<Button
|
<Button
|
||||||
sx={{ marginLeft: '150px', marginTop: '10px', backgroundColor: theme.colors.primary.main, color: 'white', '&:hover': { backgroundColor: theme.colors.primary.dark }, padding: '6px 8px' }}
|
sx={{
|
||||||
onClick={() => setOpenFolder(false)}
|
marginLeft: '150px',
|
||||||
|
marginTop: '10px',
|
||||||
|
backgroundColor: theme.colors.primary.main,
|
||||||
|
color: 'white',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.colors.primary.dark
|
||||||
|
},
|
||||||
|
padding: '6px 8px'
|
||||||
|
}}
|
||||||
|
onClick={handleCloseFolder}
|
||||||
>
|
>
|
||||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -263,26 +326,52 @@ function UserAccess(props: UserAccessProps) {
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<FormControl fullWidth sx={{ marginTop: 2, width: 390 }}>
|
<FormControl fullWidth sx={{ marginTop: 2, width: 390 }}>
|
||||||
<InputLabel sx={{ fontSize: 14, backgroundColor: 'white' }}>
|
<InputLabel
|
||||||
<FormattedMessage id="grantAccessToInstallations" defaultMessage="Grant access to installations" />
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="grantAccessToInstallations"
|
||||||
|
defaultMessage="Grant access to installations"
|
||||||
|
/>
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
value={selectedInstallationNames}
|
value={selectedInstallationNames}
|
||||||
onChange={(e) => setSelectedInstallationNames(e.target.value as string[])}
|
onChange={handleInstallationChange}
|
||||||
open={openInstallation}
|
open={openInstallation}
|
||||||
onClose={() => setOpenInstallation(false)}
|
onClose={handleCloseInstallation}
|
||||||
onOpen={() => setOpenInstallation(true)}
|
onOpen={handleOpenInstallation}
|
||||||
renderValue={(selected) => (
|
renderValue={(selected) => (
|
||||||
<div>{selected.map((i) => <span key={i}>{i}, </span>)}</div>
|
<div>
|
||||||
|
{selected.map((installation) => (
|
||||||
|
<span key={installation}>{installation}, </span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{availableInstallations.map((installation) => (
|
{installations.map((installation) => (
|
||||||
<MenuItem key={installation.id} value={installation.name}>{installation.name}</MenuItem>
|
<MenuItem
|
||||||
|
key={installation.id}
|
||||||
|
value={installation.name}
|
||||||
|
>
|
||||||
|
{installation.name}
|
||||||
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
<Button
|
<Button
|
||||||
sx={{ marginLeft: '150px', marginTop: '10px', backgroundColor: theme.colors.primary.main, color: 'white', '&:hover': { backgroundColor: theme.colors.primary.dark }, padding: '6px 8px' }}
|
sx={{
|
||||||
onClick={() => setOpenInstallation(false)}
|
marginLeft: '150px',
|
||||||
|
marginTop: '10px',
|
||||||
|
backgroundColor: theme.colors.primary.main,
|
||||||
|
color: 'white',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.colors.primary.dark
|
||||||
|
},
|
||||||
|
padding: '6px 8px'
|
||||||
|
}}
|
||||||
|
onClick={handleCloseInstallation}
|
||||||
>
|
>
|
||||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -291,15 +380,32 @@ function UserAccess(props: UserAccessProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<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}
|
onClick={handleSubmit}
|
||||||
>
|
>
|
||||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => setOpenModal(false)}
|
onClick={handleCancel}
|
||||||
sx={{ marginTop: '20px', marginLeft: '10px', backgroundColor: theme.colors.primary.main, color: 'white', '&:hover': { backgroundColor: theme.colors.primary.dark }, padding: '6px 8px' }}
|
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" />
|
<FormattedMessage id="cancel" defaultMessage="Cancel" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -310,63 +416,43 @@ function UserAccess(props: UserAccessProps) {
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleGrantAccess}
|
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" />
|
<FormattedMessage id="grantAccess" defaultMessage="Grant Access" />
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Folder Access Section */}
|
|
||||||
<Grid item xs={12} md={12}>
|
<Grid item xs={12} md={12}>
|
||||||
<Typography variant="subtitle2" sx={{ mt: 1, mb: 0.5, color: 'text.secondary', fontWeight: 600 }}>
|
{accessibleInstallationsForUser.map((installation, index) => {
|
||||||
<FormattedMessage id="folderAccess" defaultMessage="Folder Access" />
|
const isLast = index === accessibleInstallationsForUser.length - 1;
|
||||||
</Typography>
|
|
||||||
{directFolders.map((folder, index) => {
|
|
||||||
const isLast = index === directFolders.length - 1;
|
|
||||||
return (
|
return (
|
||||||
<Fragment key={folder.id}>
|
<Fragment key={installation.name}>
|
||||||
<ListItem
|
<ListItem
|
||||||
sx={{ mb: isLast ? 1 : 0 }}
|
sx={{
|
||||||
|
mb: isLast ? 4 : 0 // Apply margin-bottom to the last item only
|
||||||
|
}}
|
||||||
secondaryAction={
|
secondaryAction={
|
||||||
currentUser.userType === UserType.admin && (
|
currentUser.userType === UserType.admin && (
|
||||||
<IconButton onClick={() => handleRevokeFolder(folder.id, folder.name)} edge="end">
|
<IconButton
|
||||||
<PersonRemoveIcon />
|
onClick={() => {
|
||||||
</IconButton>
|
RevokeAccessFromResource(
|
||||||
)
|
'ToInstallation',
|
||||||
}
|
props.current_user.id,
|
||||||
>
|
'InstallationId',
|
||||||
<ListItemAvatar>
|
installation.id,
|
||||||
<Avatar>
|
props.current_user.name
|
||||||
<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 */}
|
fetchInstallationsForUser(props.current_user.id);
|
||||||
<Grid item xs={12} md={12}>
|
}}
|
||||||
<Typography variant="subtitle2" sx={{ mt: 2, mb: 0.5, color: 'text.secondary', fontWeight: 600 }}>
|
edge="end"
|
||||||
<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 />
|
<PersonRemoveIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)
|
)
|
||||||
|
|
@ -383,9 +469,22 @@ function UserAccess(props: UserAccessProps) {
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{directInstallations.length === 0 && (
|
|
||||||
<Alert severity="info" sx={{ mb: 4 }}>
|
{accessibleInstallationsForUser.length == 0 && (
|
||||||
<FormattedMessage id="noDirectInstallationAccess" defaultMessage="No direct installation access grants" />
|
<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>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
|
|
@ -474,18 +474,16 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{currentUser.userType !== UserType.client && (
|
<Route
|
||||||
<Route
|
path={routes.log}
|
||||||
path={routes.log}
|
element={
|
||||||
element={
|
<Log
|
||||||
<Log
|
errorLoadingS3Data={errorLoadingS3Data}
|
||||||
errorLoadingS3Data={errorLoadingS3Data}
|
id={props.current_installation.id}
|
||||||
id={props.current_installation.id}
|
status={props.current_installation.status}
|
||||||
status={props.current_installation.status}
|
></Log>
|
||||||
></Log>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={routes.live}
|
path={routes.live}
|
||||||
|
|
@ -499,20 +497,18 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{currentUser.userType !== UserType.client && (
|
<Route
|
||||||
<Route
|
path={routes.batteryview + '/*'}
|
||||||
path={routes.batteryview + '/*'}
|
element={
|
||||||
element={
|
<BatteryViewSodioHome
|
||||||
<BatteryViewSodioHome
|
values={values}
|
||||||
values={values}
|
s3Credentials={s3Credentials}
|
||||||
s3Credentials={s3Credentials}
|
installationId={props.current_installation.id}
|
||||||
installationId={props.current_installation.id}
|
installation={props.current_installation}
|
||||||
installation={props.current_installation}
|
connected={connected}
|
||||||
connected={connected}
|
></BatteryViewSodioHome>
|
||||||
></BatteryViewSodioHome>
|
}
|
||||||
}
|
></Route>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
{currentUser.userType == UserType.admin && (
|
||||||
<Route
|
<Route
|
||||||
|
|
@ -563,14 +559,16 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
{currentUser.userType == UserType.admin && (
|
||||||
path={routes.report}
|
<Route
|
||||||
element={
|
path={routes.report}
|
||||||
<WeeklyReport
|
element={
|
||||||
installationId={props.current_installation.id}
|
<WeeklyReport
|
||||||
/>
|
installationId={props.current_installation.id}
|
||||||
}
|
/>
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={'*'}
|
path={'*'}
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,7 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
value: 'log',
|
value: 'log',
|
||||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
value: 'manage',
|
value: 'manage',
|
||||||
label: (
|
label: (
|
||||||
|
|
@ -127,12 +128,14 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
value: 'information',
|
value: 'information',
|
||||||
label: (
|
label: (
|
||||||
<FormattedMessage id="information" defaultMessage="Information" />
|
<FormattedMessage id="information" defaultMessage="Information" />
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
value: 'configuration',
|
value: 'configuration',
|
||||||
label: (
|
label: (
|
||||||
|
|
@ -161,45 +164,6 @@ 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',
|
value: 'live',
|
||||||
|
|
@ -214,24 +178,14 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
label: (
|
label: (
|
||||||
<FormattedMessage id="information" defaultMessage="Information" />
|
<FormattedMessage id="information" defaultMessage="Information" />
|
||||||
)
|
)
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'report',
|
|
||||||
label: (
|
|
||||||
<FormattedMessage
|
|
||||||
id="report"
|
|
||||||
defaultMessage="Report"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const inInstallationView =
|
const tabs =
|
||||||
currentTab != 'list' &&
|
currentTab != 'list' &&
|
||||||
currentTab != 'tree' &&
|
currentTab != 'tree' &&
|
||||||
!location.pathname.includes('folder');
|
!location.pathname.includes('folder') &&
|
||||||
|
currentUser.userType == UserType.admin
|
||||||
const tabs = inInstallationView && currentUser.userType == UserType.admin
|
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
value: 'list',
|
value: 'list',
|
||||||
|
|
@ -262,6 +216,7 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
value: 'log',
|
value: 'log',
|
||||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
value: 'manage',
|
value: 'manage',
|
||||||
label: (
|
label: (
|
||||||
|
|
@ -271,6 +226,7 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
value: 'information',
|
value: 'information',
|
||||||
label: (
|
label: (
|
||||||
|
|
@ -305,7 +261,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: inInstallationView && currentUser.userType == UserType.partner
|
: currentTab != 'list' &&
|
||||||
|
currentTab != 'tree' &&
|
||||||
|
!location.pathname.includes('folder') &&
|
||||||
|
currentUser.userType == UserType.client
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
value: 'list',
|
value: 'list',
|
||||||
|
|
@ -323,67 +282,12 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
value: 'overview',
|
value: 'overview',
|
||||||
label: <FormattedMessage id="overview" defaultMessage="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',
|
value: 'information',
|
||||||
label: (
|
label: (
|
||||||
<FormattedMessage id="information" defaultMessage="Information" />
|
<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',
|
value: 'information',
|
||||||
label: <FormattedMessage id="information" defaultMessage="Information" />
|
label: <FormattedMessage id="information" defaultMessage="Information" />
|
||||||
},
|
},
|
||||||
...(currentUser.userType === UserType.admin ? [{
|
{
|
||||||
value: 'manage',
|
value: 'manage',
|
||||||
label: (
|
label: (
|
||||||
<FormattedMessage id="manageAccess" defaultMessage="Manage Access" />
|
<FormattedMessage id="manageAccess" defaultMessage="Manage Access" />
|
||||||
)
|
)
|
||||||
}] : [])
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue