Skip to content

Commit

Permalink
Merge pull request #34 from SimonDmz/feature-admin-role
Browse files Browse the repository at this point in the history
Feature admin role
  • Loading branch information
SimonDmz authored Jan 5, 2023
2 parents d0e9b31 + ff32200 commit 60bf710
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 50 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "massive-attack",
"version": "2.0.5",
"private": true,
"version": "2.1.0",
"private": true,
"dependencies": {
"@material-ui/core": "^4.11.4",
"@material-ui/icons": "^4.11.2",
Expand Down
12 changes: 10 additions & 2 deletions src/components/app/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import React, { useEffect, useState } from 'react';

import Header from '../header';
import Navigation from '../navigation';
import OrganisationUnitsVue from '../orgaUnitsVue';
import Preloader from '../common/Preloader';
import Requester from '../requester';
import TrainingCourses from '../trainingCourses';
import { Typography } from '@material-ui/core';
import { getConfiguration } from '../../utils/configuration';
import { getUser } from '../../utils/userInfo';
import { getUserOrganisationalUnit } from '../../utils/api/massive-attack-api';
Expand All @@ -14,7 +16,7 @@ import { useAuth } from '../../utils/hook/auth';
export const AppContext = React.createContext();

const App = () => {
const { authenticated, isAdmin } = useAuth();
const { authenticated, isAdmin, pending } = useAuth();
const [organisationalUnit, setOrganisationalUnit] = useState();
const [pf, setPf] = useState('');

Expand All @@ -36,7 +38,12 @@ const App = () => {

return (
<div>
{!authenticated && <Preloader message="Patientez" />}
{pending && <Preloader message="Patientez" />}
{!pending && !authenticated && (
<Typography variant="h2" color="error">
Vous n'avez pas les droits pour vous connecter à cette application.
</Typography>
)}
{authenticated && (
<>
<Header user={getUser()} pf={pf} />
Expand All @@ -51,6 +58,7 @@ const App = () => {
<Switch>
<Route exact path="/" component={Requester} />
<Route path="/training-courses" component={TrainingCourses} />
<Route path="/organisation-units-vue" component={OrganisationUnitsVue} />
</Switch>
</>
)}
Expand Down
14 changes: 13 additions & 1 deletion src/components/navigation/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Paper, Tab, Tabs } from '@material-ui/core';
import React, { useContext } from 'react';

import { AppContext } from '../app/App';
import { Link } from 'react-router-dom';
import React from 'react';
import StarIcon from '@material-ui/icons/Star';
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles({
Expand All @@ -12,7 +14,9 @@ const useStyles = makeStyles({

const Navigation = ({ location }) => {
const classes = useStyles();
const { isAdmin } = useContext(AppContext);

const adminIcon = <StarIcon color="error"></StarIcon>;
return (
<Paper className={classes.root}>
<Tabs value={location.pathname}>
Expand All @@ -23,6 +27,14 @@ const Navigation = ({ location }) => {
component={Link}
to="/training-courses"
></Tab>
<Tab
label="Administration"
value="/organisation-units-vue"
component={Link}
to="/organisation-units-vue"
icon={adminIcon}
disabled={!isAdmin}
></Tab>
</Tabs>
</Paper>
);
Expand Down
173 changes: 173 additions & 0 deletions src/components/orgaUnitsVue/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import {
Button,
Dialog,
IconButton,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
makeStyles,
} from '@material-ui/core';
import React, { useContext, useEffect, useState } from 'react';
import { deleteCampaign, getCampaigns } from '../../utils/api/massive-attack-api';

import { AppContext } from '../app/App';
import DeleteIcon from '@material-ui/icons/Delete';
import Preloader from '../common/Preloader';
import { getConfiguration } from '../../utils/configuration/index';

const useStyles = makeStyles(() => ({
row: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
margin: '1em',
},
}));

const OrganisationUnitsVue = () => {
const classes = useStyles();

const { isAdmin = false } = useContext(AppContext);
const [campaigns, setCampaigns] = useState([]);
const [sessions, setSessions] = useState([]);
const [openModal, setOpenModal] = useState(false);
const [sessionToDelete, setSessionToDelete] = useState();
const [filteredCamps, setFilteredCamps] = useState([]);
const [waiting, setWaiting] = useState(false);

useEffect(() => {
setFilteredCamps(campaigns.filter(({ id }) => id.includes('_')));
}, [campaigns, isAdmin]);

useEffect(() => {
const fetchCampaigns = async () => {
const { MASSIVE_ATTACK_API_URL, AUTHENTICATION_MODE, PLATEFORM } = await getConfiguration();
const camps = await getCampaigns(
MASSIVE_ATTACK_API_URL,
AUTHENTICATION_MODE,
PLATEFORM,
isAdmin
);
setCampaigns(await camps.data);
};
fetchCampaigns();
}, [isAdmin]);

useEffect(() => {
const getOrganisationalUnit = id => id.split('_')[2];
const getType = id => id.split('_')[1];

const buildSessions = camps => {
const buildSession = timeStampType => {
const [ouid, type] = timeStampType.split('_');
const ouIdedCampaigns = camps
.map(camp => {
const { id } = camp;
return { ...camp, ouid: getOrganisationalUnit(id), type: getType(id) };
})
.filter(camp => camp.ouid === ouid && camp.type === type);
const sessionType = ouIdedCampaigns[0].type;
const sessionOrganisationUnit = getOrganisationalUnit(ouIdedCampaigns[0].id);

return {
ouid,
campaigns: ouIdedCampaigns,
type: sessionType,
organisationUnit: sessionOrganisationUnit,
};
};

const timestamps = camps
.map(camp => camp.id)
.map(id => getOrganisationalUnit(id) + '_' + getType(id));
const uniqTimestamps = [...new Set(timestamps)];

return uniqTimestamps.map(time => buildSession(time)).sort((a, b) => a.ouid > b.ouid);
};

setSessions(buildSessions(filteredCamps));
}, [filteredCamps]);

const deleteCampaignById = async id => {
const { MASSIVE_ATTACK_API_URL, AUTHENTICATION_MODE, PLATEFORM } = await getConfiguration();
return deleteCampaign(MASSIVE_ATTACK_API_URL, AUTHENTICATION_MODE, PLATEFORM)(id);
};

const deleteCampaignsBySession = session => {
const campaignsToDelete = session.campaigns;
setWaiting(true);
setCampaigns(campaigns.filter(camp => !campaignsToDelete.map(c => c.id).includes(camp.id)));
const promises = campaignsToDelete.map(camp => deleteCampaignById(camp.id));
Promise.all(promises).then(() => setWaiting(false));
};

const handleClose = () => {
setOpenModal(false);
};

const confirmDeletion = () => {
deleteCampaignsBySession(sessionToDelete);
handleClose();
};

const selectSession = session => {
setSessionToDelete(session);
setOpenModal(true);
};

return (
<>
{waiting && <Preloader message="Patientez" />}

{!waiting && (
<>
<Typography variant="h6">Liste des sessions de formation</Typography>
<TableContainer component={Paper}>
<Table aria-label="training courses table">
<TableHead>
<TableRow>
<TableCell align="center">Unité organisationnelle</TableCell>
<TableCell align="center">Type de formation</TableCell>
<TableCell align="center">Nombre de campagnes</TableCell>
<TableCell align="center">Supprimer</TableCell>
</TableRow>
</TableHead>
<TableBody>
{sessions.map(session => {
return (
<TableRow key={session.timeStamp}>
<TableCell align="center">{session.organisationUnit}</TableCell>
<TableCell align="center">
{session.type === 'I' ? 'Collecte' : 'Gestion'}
</TableCell>
<TableCell align="center">{session.campaigns.length}</TableCell>
<TableCell align="center">
<IconButton aria-label="delete" onClick={() => selectSession(session)}>
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
<Dialog onClose={handleClose} open={openModal}>
<Typography variant="h6">{`Suppression des ${sessionToDelete?.campaigns?.length} ${sessionToDelete?.type} ${sessionToDelete?.ouid}`}</Typography>
<div className={classes.row}>
<Button onClick={handleClose}>Annuler</Button>
<Button onClick={confirmDeletion}>Valider</Button>
</div>
</Dialog>
</>
)}
</>
);
};

export default OrganisationUnitsVue;
4 changes: 3 additions & 1 deletion src/components/trainingCourses/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ const TrainingCourses = () => {
return orgaUnit === organisationalUnit?.id;
};

setFilteredCamps(campaigns.filter(camp => isVisibleCampaign(camp)));
setFilteredCamps(
campaigns.filter(({ id }) => id.includes('_')).filter(camp => isVisibleCampaign(camp))
);
}, [campaigns, organisationalUnit?.id, isAdmin]);

useEffect(() => {
Expand Down
86 changes: 42 additions & 44 deletions src/utils/hook/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,68 @@ import { GUEST_USER, LOCALE_STORAGE_USER_KEY } from './../constants';
import { getTokenInfo, keycloakAuthentication } from './../keycloak';
import { useEffect, useState } from 'react';

// import useInterval from './useInterval';

export const useAuth = () => {
const [authenticated, setAuthenticated] = useState(false);
const [pending, setPending] = useState(true);
const [isAdmin, setIsAdmin] = useState(false);
// const refreshFunction = useRef(() => {});
// useInterval(refreshFunction.current, 60000);

// const kcRefresh = () => refreshToken(-1);

const accessAuthorized = () => {
setAuthenticated(true);
setPending(false);
};

const accessDenied = () => {
setAuthenticated(false);
setPending(false);
};

const anyMatch = (checkedRoles, targetRoles) =>
checkedRoles.filter(r => targetRoles.includes(r)).length > 0;

useEffect(() => {
const configURL = `${window.location.origin}/configuration.json`;
fetch(configURL)
.then(response => response.json())
.then(data => {
switch (data.AUTHENTICATION_MODE) {
case 'anonymous':
window.localStorage.setItem(LOCALE_STORAGE_USER_KEY, JSON.stringify(GUEST_USER));
accessAuthorized();
setIsAdmin(true);
break;
if (pending) {
fetch(configURL)
.then(response => response.json())
.then(data => {
switch (data.AUTHENTICATION_MODE) {
case 'anonymous':
window.localStorage.setItem(LOCALE_STORAGE_USER_KEY, JSON.stringify(GUEST_USER));
accessAuthorized();
setIsAdmin(true);
break;

case 'keycloak':
if (!authenticated) {
keycloakAuthentication({
onLoad: 'login-required',
checkLoginIframe: false,
})
.then(auth => {
if (auth) {
const userInfos = getTokenInfo();
const { roles } = userInfos;
if (anyMatch(roles, [...data.USER_ROLES, data.ADMIN_ROLE])) {
window.localStorage.setItem(
LOCALE_STORAGE_USER_KEY,
JSON.stringify(userInfos)
);
// refreshFunction.current = kcRefresh;
accessAuthorized();
setIsAdmin(anyMatch(roles, [data.ADMIN_ROLE]));
case 'keycloak':
if (!authenticated) {
keycloakAuthentication({
onLoad: 'login-required',
checkLoginIframe: false,
})
.then(auth => {
if (auth) {
const userInfos = getTokenInfo();
const { roles } = userInfos;
if (anyMatch(roles, [...data.USER_ROLES, data.ADMIN_ROLE])) {
window.localStorage.setItem(
LOCALE_STORAGE_USER_KEY,
JSON.stringify(userInfos)
);
accessAuthorized();
setIsAdmin(anyMatch(roles, [data.ADMIN_ROLE]));
} else {
accessDenied();
}
} else {
accessDenied();
}
} else {
accessDenied();
}
})
.catch(() => accessDenied());
}
break;
default:
}
});
})
.catch(() => accessDenied());
}
break;
default:
}
});
}
});
return { authenticated, isAdmin };
return { authenticated, isAdmin, pending };
};

0 comments on commit 60bf710

Please sign in to comment.