Compare commits
No commits in common. "ffc5b124100186c239ba08b3cdd1af47f5f815c2" and "444a0a29f25559a3afde1bf05f88313fc73df76f" have entirely different histories.
ffc5b12410
...
444a0a29f2
|
|
@ -522,7 +522,7 @@ public class Controller : ControllerBase
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
|
|
||||||
return user.DirectlyAccessibleInstallations()
|
return user.DirectlyAccessibleInstallations()
|
||||||
.Select(i => new { i.Id, i.Name, i.Product })
|
.Select(i => new { i.Id, i.Name })
|
||||||
.ToList<Object>();
|
.ToList<Object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -542,24 +542,6 @@ public class Controller : ControllerBase
|
||||||
.ToList<Object>();
|
.ToList<Object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet(nameof(GetInstallationsUnderFolder))]
|
|
||||||
public ActionResult<IEnumerable<Object>> GetInstallationsUnderFolder(Int64 folderId, Token authToken)
|
|
||||||
{
|
|
||||||
var sessionUser = Db.GetSession(authToken)?.User;
|
|
||||||
if (sessionUser == null)
|
|
||||||
return Unauthorized();
|
|
||||||
|
|
||||||
var folder = Db.GetFolderById(folderId);
|
|
||||||
if (folder == null || !sessionUser.HasAccessTo(folder))
|
|
||||||
return Unauthorized();
|
|
||||||
|
|
||||||
return folder
|
|
||||||
.DescendantFoldersAndSelf() // self + all nested subfolders
|
|
||||||
.SelectMany(f => f.ChildInstallations()) // installations directly in each
|
|
||||||
.Select(i => new { i.Id, i.Name, i.Product })
|
|
||||||
.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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -10,18 +10,13 @@ import {
|
||||||
Alert,
|
Alert,
|
||||||
Autocomplete,
|
Autocomplete,
|
||||||
Box,
|
Box,
|
||||||
CircularProgress,
|
|
||||||
Collapse,
|
|
||||||
Container,
|
Container,
|
||||||
Divider,
|
Divider,
|
||||||
FormControl,
|
FormControl,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
Link,
|
|
||||||
List,
|
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemButton,
|
|
||||||
ListSubheader,
|
ListSubheader,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Modal,
|
Modal,
|
||||||
|
|
@ -37,9 +32,6 @@ 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 FolderIcon from '@mui/icons-material/Folder';
|
||||||
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
|
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
||||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
|
||||||
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';
|
||||||
|
|
@ -51,8 +43,6 @@ import {
|
||||||
I_Installation
|
I_Installation
|
||||||
} from '../../../interfaces/InstallationTypes';
|
} from '../../../interfaces/InstallationTypes';
|
||||||
import axiosConfig from '../../../Resources/axiosConfig';
|
import axiosConfig from '../../../Resources/axiosConfig';
|
||||||
import routes from 'src/Resources/routes.json';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
const PRODUCT_GROUP_ORDER: number[] = [2, 5, 4, 3, 0, 1];
|
const PRODUCT_GROUP_ORDER: number[] = [2, 5, 4, 3, 0, 1];
|
||||||
const PRODUCT_NAMES: Record<number, string> = {
|
const PRODUCT_NAMES: Record<number, string> = {
|
||||||
|
|
@ -101,18 +91,7 @@ function UserAccess(props: UserAccessProps) {
|
||||||
|
|
||||||
// Direct grants for this user
|
// Direct grants for this user
|
||||||
const [directFolders, setDirectFolders] = useState<{ id: number; name: string }[]>([]);
|
const [directFolders, setDirectFolders] = useState<{ id: number; name: string }[]>([]);
|
||||||
const [directInstallations, setDirectInstallations] = useState<
|
const [directInstallations, setDirectInstallations] = useState<{ id: number; name: string }[]>([]);
|
||||||
{ id: number; name: string; product: number }[]
|
|
||||||
>([]);
|
|
||||||
|
|
||||||
// Installations under each folder (lazy-loaded + cached), with expand/loading state
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [expandedFolderId, setExpandedFolderId] = useState<number | null>(null);
|
|
||||||
const [folderInstallations, setFolderInstallations] = useState<
|
|
||||||
Record<number, { id: number; name: string; product: number }[]>
|
|
||||||
>({});
|
|
||||||
const [loadingFolderId, setLoadingFolderId] = useState<number | null>(null);
|
|
||||||
const [folderError, setFolderError] = useState<Record<number, boolean>>({});
|
|
||||||
|
|
||||||
const accessContext = useContext(AccessContext);
|
const accessContext = useContext(AccessContext);
|
||||||
const {
|
const {
|
||||||
|
|
@ -190,49 +169,6 @@ function UserAccess(props: UserAccessProps) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build the route to an installation from its product (mirrors CustomTreeItem).
|
|
||||||
// Targets the Tickets tab; the installation router redirects to `live` when the
|
|
||||||
// Tickets tab isn't available (non-admin), via its catch-all <Navigate to live>.
|
|
||||||
const installationLink = (inst: { id: number; product: number }): string => {
|
|
||||||
const base =
|
|
||||||
inst.product === 0
|
|
||||||
? routes.installations
|
|
||||||
: inst.product === 1
|
|
||||||
? routes.salidomo_installations
|
|
||||||
: inst.product === 2
|
|
||||||
? routes.sodiohome_installations
|
|
||||||
: inst.product === 4
|
|
||||||
? routes.sodistoregrid_installations
|
|
||||||
: inst.product === 5
|
|
||||||
? routes.sodistorepro_installations
|
|
||||||
: routes.sodistore_installations;
|
|
||||||
return base + routes.tree + routes.installation + inst.id + '/' + routes.installationTickets;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchFolderInstallations = async (folderId: number) => {
|
|
||||||
setLoadingFolderId(folderId);
|
|
||||||
setFolderError((prev) => ({ ...prev, [folderId]: false }));
|
|
||||||
try {
|
|
||||||
const res = await axiosConfig.get(`/GetInstallationsUnderFolder?folderId=${folderId}`);
|
|
||||||
setFolderInstallations((prev) => ({ ...prev, [folderId]: res.data }));
|
|
||||||
} catch (err) {
|
|
||||||
if (err.response && err.response.status === 401) removeToken();
|
|
||||||
else setFolderError((prev) => ({ ...prev, [folderId]: true }));
|
|
||||||
} finally {
|
|
||||||
setLoadingFolderId(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleToggleFolder = (folderId: number) => {
|
|
||||||
if (expandedFolderId === folderId) {
|
|
||||||
setExpandedFolderId(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setExpandedFolderId(folderId);
|
|
||||||
if (folderInstallations[folderId]) return; // already cached
|
|
||||||
fetchFolderInstallations(folderId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRevokeInstallation = async (installationId: number) => {
|
const handleRevokeInstallation = async (installationId: number) => {
|
||||||
axiosConfig
|
axiosConfig
|
||||||
.post(`/RevokeUserAccessToInstallation?UserId=${props.current_user.id}&InstallationId=${installationId}`)
|
.post(`/RevokeUserAccessToInstallation?UserId=${props.current_user.id}&InstallationId=${installationId}`)
|
||||||
|
|
@ -421,27 +357,16 @@ function UserAccess(props: UserAccessProps) {
|
||||||
</Typography>
|
</Typography>
|
||||||
{directFolders.map((folder, index) => {
|
{directFolders.map((folder, index) => {
|
||||||
const isLast = index === directFolders.length - 1;
|
const isLast = index === directFolders.length - 1;
|
||||||
const isExpanded = expandedFolderId === folder.id;
|
|
||||||
const installations = folderInstallations[folder.id];
|
|
||||||
return (
|
return (
|
||||||
<Fragment key={folder.id}>
|
<Fragment key={folder.id}>
|
||||||
<ListItem
|
<ListItem
|
||||||
sx={{ mb: isLast && !isExpanded ? 1 : 0 }}
|
sx={{ mb: isLast ? 1 : 0 }}
|
||||||
secondaryAction={
|
secondaryAction={
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
currentUser.userType === UserType.admin && (
|
||||||
<IconButton
|
<IconButton onClick={() => handleRevokeFolder(folder.id, folder.name)} edge="end">
|
||||||
onClick={() => handleToggleFolder(folder.id)}
|
<PersonRemoveIcon />
|
||||||
edge="end"
|
|
||||||
aria-label={intl.formatMessage({ id: 'viewInstallations' })}
|
|
||||||
>
|
|
||||||
{isExpanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{currentUser.userType === UserType.admin && (
|
)
|
||||||
<IconButton onClick={() => handleRevokeFolder(folder.id, folder.name)} edge="end">
|
|
||||||
<PersonRemoveIcon />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
|
|
@ -451,60 +376,6 @@ function UserAccess(props: UserAccessProps) {
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText primary={folder.name} />
|
<ListItemText primary={folder.name} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
|
||||||
{loadingFolderId === folder.id ? (
|
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 1.5 }}>
|
|
||||||
<CircularProgress size={20} />
|
|
||||||
</Box>
|
|
||||||
) : folderError[folder.id] ? (
|
|
||||||
<Alert
|
|
||||||
severity="error"
|
|
||||||
sx={{ ml: 6, mr: 2, my: 1 }}
|
|
||||||
action={
|
|
||||||
<Button
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
onClick={() => fetchFolderInstallations(folder.id)}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="retry" defaultMessage="Retry" />
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="couldNotLoadInstallations"
|
|
||||||
defaultMessage="Could not load installations"
|
|
||||||
/>
|
|
||||||
</Alert>
|
|
||||||
) : installations && installations.length > 0 ? (
|
|
||||||
<List disablePadding sx={{ pl: 6 }}>
|
|
||||||
{installations.map((inst) => (
|
|
||||||
<ListItemButton
|
|
||||||
key={inst.id}
|
|
||||||
onClick={() => navigate(installationLink(inst))}
|
|
||||||
sx={{ py: 0.25 }}
|
|
||||||
>
|
|
||||||
<ListItemAvatar sx={{ minWidth: 36 }}>
|
|
||||||
<InsertDriveFileIcon fontSize="small" color="action" />
|
|
||||||
</ListItemAvatar>
|
|
||||||
<ListItemText
|
|
||||||
primary={
|
|
||||||
<Link component="span" underline="hover">
|
|
||||||
{inst.name}
|
|
||||||
</Link>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItemButton>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
) : installations ? (
|
|
||||||
<Typography variant="body2" sx={{ pl: 6, py: 1, color: 'text.secondary' }}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="noInstallationsInFolder"
|
|
||||||
defaultMessage="No installations in this folder"
|
|
||||||
/>
|
|
||||||
</Typography>
|
|
||||||
) : null}
|
|
||||||
</Collapse>
|
|
||||||
<Divider />
|
<Divider />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
@ -540,19 +411,7 @@ function UserAccess(props: UserAccessProps) {
|
||||||
<PersonIcon />
|
<PersonIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText
|
<ListItemText primary={installation.name} />
|
||||||
primary={
|
|
||||||
<Link
|
|
||||||
component="button"
|
|
||||||
type="button"
|
|
||||||
underline="hover"
|
|
||||||
align="left"
|
|
||||||
onClick={() => navigate(installationLink(installation))}
|
|
||||||
>
|
|
||||||
{installation.name}
|
|
||||||
</Link>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider />
|
<Divider />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
|
||||||
|
|
@ -78,26 +78,9 @@ function TicketList() {
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Group partner names that differ only by case / whitespace so the same
|
|
||||||
// partner does not appear as multiple dropdown entries (e.g. "Breu AG" vs
|
|
||||||
// "Breu Ag"). Normalize for matching, but show one canonical label per group.
|
|
||||||
const normalizePartner = (p: string) =>
|
|
||||||
p.trim().replace(/\s+/g, ' ').toLowerCase();
|
|
||||||
|
|
||||||
const partnerOptions = Array.from(
|
const partnerOptions = Array.from(
|
||||||
tickets
|
new Set(tickets.map((t) => t.distributionPartner).filter((p) => p && p.trim() !== ''))
|
||||||
.map((t) => t.distributionPartner)
|
).sort();
|
||||||
.filter((p) => p && p.trim() !== '')
|
|
||||||
.reduce((map, p) => {
|
|
||||||
const key = normalizePartner(p);
|
|
||||||
const display = p.trim().replace(/\s+/g, ' ');
|
|
||||||
const current = map.get(key);
|
|
||||||
// One canonical label per group; pick the alphabetically first spelling.
|
|
||||||
if (!current || display.localeCompare(current) < 0) map.set(key, display);
|
|
||||||
return map;
|
|
||||||
}, new Map<string, string>())
|
|
||||||
.values()
|
|
||||||
).sort((a, b) => a.localeCompare(b));
|
|
||||||
|
|
||||||
const assigneeOptions = adminUsers
|
const assigneeOptions = adminUsers
|
||||||
.filter((u) => {
|
.filter((u) => {
|
||||||
|
|
@ -116,9 +99,7 @@ function TicketList() {
|
||||||
t.subject.toLowerCase().includes(search.toLowerCase()) ||
|
t.subject.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
t.installationName.toLowerCase().includes(search.toLowerCase());
|
t.installationName.toLowerCase().includes(search.toLowerCase());
|
||||||
const matchesStatus = statusFilter.length === 0 || statusFilter.includes(t.status);
|
const matchesStatus = statusFilter.length === 0 || statusFilter.includes(t.status);
|
||||||
const matchesPartner =
|
const matchesPartner = partnerFilter === '' || t.distributionPartner === partnerFilter;
|
||||||
partnerFilter === '' ||
|
|
||||||
normalizePartner(t.distributionPartner ?? '') === normalizePartner(partnerFilter);
|
|
||||||
const matchesAssignee =
|
const matchesAssignee =
|
||||||
assigneeFilter === '' ||
|
assigneeFilter === '' ||
|
||||||
(assigneeFilter === '__unassigned__'
|
(assigneeFilter === '__unassigned__'
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import {
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
|
||||||
import { InnovEnergyUser } from 'src/interfaces/UserTypes';
|
import { InnovEnergyUser } from 'src/interfaces/UserTypes';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
|
|
||||||
|
|
@ -23,25 +22,14 @@ interface FlatUsersViewProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const FlatUsersView = (props: FlatUsersViewProps) => {
|
const FlatUsersView = (props: FlatUsersViewProps) => {
|
||||||
// Selected user is kept in the URL (?userId=) so navigating away to an
|
const [selectedUser, setSelectedUser] = useState<number>(-1);
|
||||||
// installation and pressing Back restores this user's detail pane.
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
const userIdParam = searchParams.get('userId');
|
|
||||||
const [selectedUser, setSelectedUser] = useState<number>(
|
|
||||||
userIdParam ? Number(userIdParam) : -1
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSelectOneUser = (userID: number): void => {
|
const handleSelectOneUser = (userID: number): void => {
|
||||||
const next = selectedUser !== userID ? userID : -1;
|
if (selectedUser != userID) {
|
||||||
setSelectedUser(next);
|
setSelectedUser(userID);
|
||||||
const params = new URLSearchParams(searchParams);
|
|
||||||
if (next === -1) {
|
|
||||||
params.delete('userId');
|
|
||||||
params.delete('tab');
|
|
||||||
} else {
|
} else {
|
||||||
params.set('userId', String(next));
|
setSelectedUser(-1);
|
||||||
}
|
}
|
||||||
setSearchParams(params, { replace: true });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
||||||
import { FormattedMessage, useIntl } from 'react-intl';
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
|
||||||
import UserAccess from '../ManageAccess/UserAccess';
|
import UserAccess from '../ManageAccess/UserAccess';
|
||||||
|
|
||||||
interface singleUserProps {
|
interface singleUserProps {
|
||||||
|
|
@ -42,11 +41,7 @@ function User(props: singleUserProps) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [updated, setUpdated] = useState(false);
|
const [updated, setUpdated] = useState(false);
|
||||||
// Active tab is kept in the URL (?tab=) alongside ?userId= so Back restores it.
|
const [currentTab, setCurrentTab] = useState<string>('user');
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
const [currentTab, setCurrentTab] = useState<string>(
|
|
||||||
searchParams.get('tab') === 'manage' ? 'manage' : 'user'
|
|
||||||
);
|
|
||||||
const [formValues, setFormValues] = useState(props.current_user);
|
const [formValues, setFormValues] = useState(props.current_user);
|
||||||
const tokencontext = useContext(TokenContext);
|
const tokencontext = useContext(TokenContext);
|
||||||
const { removeToken } = tokencontext;
|
const { removeToken } = tokencontext;
|
||||||
|
|
@ -91,9 +86,6 @@ function User(props: singleUserProps) {
|
||||||
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
||||||
setCurrentTab(value);
|
setCurrentTab(value);
|
||||||
setError(false);
|
setError(false);
|
||||||
const params = new URLSearchParams(searchParams);
|
|
||||||
params.set('tab', value);
|
|
||||||
setSearchParams(params, { replace: true });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
|
|
|
||||||
|
|
@ -157,10 +157,6 @@
|
||||||
"errorOccured": "Ein Fehler ist aufgetreten",
|
"errorOccured": "Ein Fehler ist aufgetreten",
|
||||||
"successfullyUpdated": "Erfolgreich aktualisiert",
|
"successfullyUpdated": "Erfolgreich aktualisiert",
|
||||||
"grantAccess": "Zugriff gewähren",
|
"grantAccess": "Zugriff gewähren",
|
||||||
"viewInstallations": "Anlagen anzeigen",
|
|
||||||
"noInstallationsInFolder": "Keine Anlagen in diesem Ordner",
|
|
||||||
"couldNotLoadInstallations": "Anlagen konnten nicht geladen werden",
|
|
||||||
"retry": "Erneut versuchen",
|
|
||||||
"UserswithDirectAccess": "Benutzer mit direktem Zugriff",
|
"UserswithDirectAccess": "Benutzer mit direktem Zugriff",
|
||||||
"UserswithInheritedAccess": "Benutzer mit vererbtem Zugriff",
|
"UserswithInheritedAccess": "Benutzer mit vererbtem Zugriff",
|
||||||
"noerrors": "Keine Fehler",
|
"noerrors": "Keine Fehler",
|
||||||
|
|
|
||||||
|
|
@ -139,10 +139,6 @@
|
||||||
"errorOccured": "An error has occurred",
|
"errorOccured": "An error has occurred",
|
||||||
"successfullyUpdated": "Successfully updated",
|
"successfullyUpdated": "Successfully updated",
|
||||||
"grantAccess": "Grant Access",
|
"grantAccess": "Grant Access",
|
||||||
"viewInstallations": "View installations",
|
|
||||||
"noInstallationsInFolder": "No installations in this folder",
|
|
||||||
"couldNotLoadInstallations": "Could not load installations",
|
|
||||||
"retry": "Retry",
|
|
||||||
"UserswithDirectAccess": "Users with Direct Access",
|
"UserswithDirectAccess": "Users with Direct Access",
|
||||||
"UserswithInheritedAccess": "Users with Inherited Access",
|
"UserswithInheritedAccess": "Users with Inherited Access",
|
||||||
"noerrors": "There are no errors",
|
"noerrors": "There are no errors",
|
||||||
|
|
|
||||||
|
|
@ -151,10 +151,6 @@
|
||||||
"errorOccured": "Une erreur s'est produite",
|
"errorOccured": "Une erreur s'est produite",
|
||||||
"successfullyUpdated": "Mise à jour réussie",
|
"successfullyUpdated": "Mise à jour réussie",
|
||||||
"grantAccess": "Accorder l'accès",
|
"grantAccess": "Accorder l'accès",
|
||||||
"viewInstallations": "Voir les installations",
|
|
||||||
"noInstallationsInFolder": "Aucune installation dans ce dossier",
|
|
||||||
"couldNotLoadInstallations": "Impossible de charger les installations",
|
|
||||||
"retry": "Réessayer",
|
|
||||||
"UserswithDirectAccess": "Utilisateurs avec accès direct",
|
"UserswithDirectAccess": "Utilisateurs avec accès direct",
|
||||||
"UserswithInheritedAccess": "Utilisateurs avec accès hérité",
|
"UserswithInheritedAccess": "Utilisateurs avec accès hérité",
|
||||||
"noerrors": "Il n'y a pas d'erreurs",
|
"noerrors": "Il n'y a pas d'erreurs",
|
||||||
|
|
|
||||||
|
|
@ -139,10 +139,6 @@
|
||||||
"errorOccured": "Si è verificato un errore",
|
"errorOccured": "Si è verificato un errore",
|
||||||
"successfullyUpdated": "Aggiornamento riuscito",
|
"successfullyUpdated": "Aggiornamento riuscito",
|
||||||
"grantAccess": "Concedi accesso",
|
"grantAccess": "Concedi accesso",
|
||||||
"viewInstallations": "Mostra installazioni",
|
|
||||||
"noInstallationsInFolder": "Nessuna installazione in questa cartella",
|
|
||||||
"couldNotLoadInstallations": "Impossibile caricare le installazioni",
|
|
||||||
"retry": "Riprova",
|
|
||||||
"UserswithDirectAccess": "Utenti con accesso diretto",
|
"UserswithDirectAccess": "Utenti con accesso diretto",
|
||||||
"UserswithInheritedAccess": "Utenti con accesso ereditato",
|
"UserswithInheritedAccess": "Utenti con accesso ereditato",
|
||||||
"noerrors": "Non ci sono errori",
|
"noerrors": "Non ci sono errori",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue