From de835a4fd3a05021a48aac3a15c557e5680347b9 Mon Sep 17 00:00:00 2001
From: Imre Toth <imre.toth@ess.eu>
Date: Tue, 22 Feb 2022 11:52:45 +0000
Subject: [PATCH] Icshwi 8500 access level refinement

---
 .../deployments/browse-deployments.feature    |  4 +-
 src/App.js                                    |  4 +-
 src/__tests__/browse-deployments-test.js      | 10 +--
 src/components/IOC/IOCDetails.js              |  7 +-
 src/components/IOC/IOCLiveStatus.js           | 27 ++++---
 src/components/IOC/IOCManage.js               | 28 ++++---
 src/components/IOC/IOCService.js              | 80 ++++++++++---------
 .../deployments/DeploymentDetails.js          | 26 +++---
 .../navigation/GlobalAppBar/GlobalAppBar.js   |  1 +
 src/views/IOC/IOCDetailsAccessControl.js      |  2 +-
 .../DeploymentDetailsAccessControl.js         |  2 +-
 .../deployments/DeploymentsAccessControl.js   |  2 +-
 src/views/deployments/DeploymentsView.js      | 13 ++-
 src/views/host/HostDetailsAccessControl.js    |  2 +-
 src/views/host/HostDetailsView.js             | 25 +++---
 15 files changed, 134 insertions(+), 99 deletions(-)

diff --git a/features/deployments/browse-deployments.feature b/features/deployments/browse-deployments.feature
index fe8a46aa..a8ef8ba3 100644
--- a/features/deployments/browse-deployments.feature
+++ b/features/deployments/browse-deployments.feature
@@ -2,7 +2,7 @@ Feature: Browse IOC Deployments
 
   Scenario: User wants to browse IOC deployments
     Given the user is not logged in and navigates to the deployments page
-    Then they should get Access deniend
+    Then they should not get Access deniend
 
 #  Scenario: Logged in user wants to browse IOC deployments
 #    Given the user is logged in
@@ -22,7 +22,7 @@ Feature: Browse IOC Deployments
 
   Scenario: User wants to navigate to a specific IOC deployment
     Given the user is not logged in and tries to navigate any deployment details page
-    Then they should get Access deniend
+    Then they should not get Access deniend
 
 #  Scenario: Logged in user wants to navigate to a specific IOC deployment
 #    Given the user is on the deployments page
diff --git a/src/App.js b/src/App.js
index 3dc71300..3178b9b9 100644
--- a/src/App.js
+++ b/src/App.js
@@ -13,8 +13,8 @@ import { IOCListView } from './views/IOC/IOCListView';
 import { HomeAccessControl } from './views/home/HomeAccessControl';
 import { GlobalAppBar } from './components/navigation/GlobalAppBar/GlobalAppBar';
 import { IOCDetailsAccessControl } from './views/IOC/IOCDetailsAccessControl';
-import { DeploymentsAccessControl } from './views/deployments/DeploymentsAccessControl'
-import { DeploymentDetailsAccessControl } from './views/deployments/DeploymentDetailsAccessControl'
+import { DeploymentsAccessControl } from './views/deployments/DeploymentsAccessControl';
+import { DeploymentDetailsAccessControl } from './views/deployments/DeploymentDetailsAccessControl';
 import { APIProvider, UserProvider } from './api/SwaggerApi';
 import { HostListView } from "./views/host/HostListView";
 import { HostDetailsAccessControl } from "./views/host/HostDetailsAccessControl";
diff --git a/src/__tests__/browse-deployments-test.js b/src/__tests__/browse-deployments-test.js
index 3aef6a61..e319273d 100644
--- a/src/__tests__/browse-deployments-test.js
+++ b/src/__tests__/browse-deployments-test.js
@@ -21,11 +21,11 @@ defineFeature(feature, (test) => {
             <App />
           </Router>
         )
-        await screen.findByText(/deployment log/i);
+        await screen.findByText(/Running/i);
       });
 
-      then('they should get Access deniend', async () => {
-        expect(await screen.findByText(/access denied/i)).toBeInTheDocument();
+      then('they should not get Access deniend', async () => {
+        expect(await screen.findByText(/IOC Name/i)).toBeInTheDocument();
       });
   });
 
@@ -59,8 +59,8 @@ defineFeature(feature, (test) => {
       await screen.findByText(/deployment details/i);
     });
 
-    then('they should get Access deniend', async () => {
-      expect(await screen.findByText(/access denied/i)).toBeInTheDocument()
+    then('they should not get Access deniend', async () => {
+      expect(await screen.findByText(/Running Undeployment/i)).toBeInTheDocument()
     });
   });
 
diff --git a/src/components/IOC/IOCDetails.js b/src/components/IOC/IOCDetails.js
index 40ebccce..1a536d3c 100644
--- a/src/components/IOC/IOCDetails.js
+++ b/src/components/IOC/IOCDetails.js
@@ -3,6 +3,7 @@ import React from "react";
 import { SimpleAccordion } from "../common/Accordion/SimpleAccordion";
 import { KeyValueTable } from "../common/KeyValueTable/KeyValueTable";
 import { IOCBadge } from "./IOCBadge";
+import AccessControl from "../auth/AccessControl";
 
 export function diff(o1, o2) {
   let diff = Object.keys(o2).reduce((diff, key) => {
@@ -29,7 +30,11 @@ export function IOCDetails({ ioc, getSubset=defaultSubset, alert, buttons }) {
         </Grid>}
         <Grid item xs={12}>
           <Box display="flex" flexDirection="row-reverse" p={2} m={1}>
-            {buttons}
+            <AccessControl 
+              allowedRoles={["DeploymentToolAdmin, DeploymentToolIntegrator"]}
+              renderNoAccess={() =><></>}>
+                {buttons}
+            </AccessControl>
           </Box>
         </Grid>
         <Grid item xs={12}>
diff --git a/src/components/IOC/IOCLiveStatus.js b/src/components/IOC/IOCLiveStatus.js
index 7af6472f..d775b35e 100644
--- a/src/components/IOC/IOCLiveStatus.js
+++ b/src/components/IOC/IOCLiveStatus.js
@@ -9,7 +9,7 @@ import GitRefLink from "./GitRefLink";
 import IOCAlerts from "./IOCAlerts";
 import { IOCService } from "./IOCService";
 import { IOCCommandTable } from "./IOCCommandTable";
-
+import AccessControl from '../../components/auth/AccessControl';
 
 export function IOCLiveStatus({ ioc, currentCommand, commands, getCommands, buttonDisabled, setButtonDisabled, setOperationQueued, 
   commandLazyParams, setCommandLazyParams, commandColumnSort, setCommandColumnSort, rowsPerPage}) {
@@ -47,19 +47,22 @@ export function IOCLiveStatus({ ioc, currentCommand, commands, getCommands, butt
 
   return (
     <>
-      <IOCDetails ioc={liveIOC} getSubset={getSubset} alert={<IOCAlerts ioc={liveIOC}/>}/>
+      <IOCDetails ioc={liveIOC} getSubset={getSubset} alert={<IOCAlerts ioc={liveIOC} />} />
       <SimpleAccordion summary="IOC Service Control log">
-        <IOCCommandTable commands={commands} 
-        commandLazyParams={commandLazyParams} setCommandLazyParams={setCommandLazyParams} commandColumnSort={commandColumnSort} setCommandColumnSort={setCommandColumnSort}
-        rowsPerPage={rowsPerPage} />
-      </SimpleAccordion>
-      <SimpleAccordion summary="ProcServLog info" defaultExpanded>
-        {liveIOC.activeDeployment?.host.csEntryId ? 
-          <LokiContainer csEntryId={liveIOC.activeDeployment?.host.csEntryId} iocName={ioc.namingName} isDeployed={isIocDeployed(ioc)} />
-          :
-          <Alert severity="error" variant="standard">Invalid CSentry ID for target host</Alert>
-        }
+        <IOCCommandTable commands={commands}
+          commandLazyParams={commandLazyParams} setCommandLazyParams={setCommandLazyParams} commandColumnSort={commandColumnSort} setCommandColumnSort={setCommandColumnSort}
+          rowsPerPage={rowsPerPage} />
       </SimpleAccordion>
+      <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]}
+        renderNoAccess={() => <></>}>
+        <SimpleAccordion summary="ProcServLog info" defaultExpanded>
+          {liveIOC.activeDeployment?.host.csEntryId ?
+            <LokiContainer csEntryId={liveIOC.activeDeployment?.host.csEntryId} iocName={ioc.namingName} isDeployed={isIocDeployed(ioc)} />
+            :
+            <Alert severity="error" variant="standard">Invalid CSentry ID for target host</Alert>
+          }
+        </SimpleAccordion>
+      </AccessControl>
     </>
   );
 }
diff --git a/src/components/IOC/IOCManage.js b/src/components/IOC/IOCManage.js
index c564615c..2e931a46 100644
--- a/src/components/IOC/IOCManage.js
+++ b/src/components/IOC/IOCManage.js
@@ -11,6 +11,7 @@ import { Link } from 'react-router-dom';
 import { DeploymentsTable } from "../../components/deployments/DeploymentsTable";
 import { SimpleAccordion } from "../common/Accordion/SimpleAccordion";
 import { initRequestParams } from "../common/Helper";
+import AccessControl from "../auth/AccessControl";
 
 export function IOCManage({ ioc, getIOC, buttonDisabled }) {
   const [deployDialogOpen, setDeployDialogOpen] = useState(false);
@@ -155,18 +156,21 @@ export function IOCManage({ ioc, getIOC, buttonDisabled }) {
             </>
           } 
         />
-        <SimpleAccordion summary="Deployment log">
-          <DeploymentsTable deployments={deployments.deploymentList} totalCount={deployments.totalCount} 
-            lazyParams={lazyParams} setLazyParams={setLazyParams} columnSort={columnSort} setColumnSort={setColumnSort} 
-            rowsPerPage={rowsPerPage} />
-        </SimpleAccordion>
-        <SimpleModal open={deployDialogOpen} setOpen={setDeployDialogOpen}>
-          <DeployIOC open={deployDialogOpen} setOpen={setDeployDialogOpen} submitCallback={closeDeployModal} init={formInit} hook={useUpdateAndDeployIoc.bind(null, ioc.id)}
-            hasActiveDeployment={ioc.activeDeployment} />
-        </SimpleModal>
-        <SimpleModal open={undeployDialogOpen} setOpen={setUndeployDialogOpen}>
-          <UndeployIOC open={undeployDialogOpen} setOpen={setUndeployDialogOpen} submitCallback={closeUndeployModal} ioc={ioc} hook={useCreateUndeployment.bind(null, ioc.id)} />
-        </SimpleModal>
+        <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]}
+          renderNoAccess={() => <></>}>
+          <SimpleAccordion summary="Deployment log">
+            <DeploymentsTable deployments={deployments.deploymentList} totalCount={deployments.totalCount}
+              lazyParams={lazyParams} setLazyParams={setLazyParams} columnSort={columnSort} setColumnSort={setColumnSort}
+              rowsPerPage={rowsPerPage} />
+          </SimpleAccordion>
+          <SimpleModal open={deployDialogOpen} setOpen={setDeployDialogOpen}>
+            <DeployIOC open={deployDialogOpen} setOpen={setDeployDialogOpen} submitCallback={closeDeployModal} init={formInit} hook={useUpdateAndDeployIoc.bind(null, ioc.id)}
+              hasActiveDeployment={ioc.activeDeployment} />
+          </SimpleModal>
+          <SimpleModal open={undeployDialogOpen} setOpen={setUndeployDialogOpen}>
+            <UndeployIOC open={undeployDialogOpen} setOpen={setUndeployDialogOpen} submitCallback={closeUndeployModal} ioc={ioc} hook={useCreateUndeployment.bind(null, ioc.id)} />
+          </SimpleModal>
+        </AccessControl>
       </>
     );
   }
diff --git a/src/components/IOC/IOCService.js b/src/components/IOC/IOCService.js
index de5aec56..734acaf4 100644
--- a/src/components/IOC/IOCService.js
+++ b/src/components/IOC/IOCService.js
@@ -9,6 +9,7 @@ import { notificationContext } from "../../components/common/notification/Notifi
 import { Alert } from "@material-ui/lab";
 import { theme } from "../../style/Theme";
 import { initRequestParams } from "../common/Helper";
+import AccessControl from "../auth/AccessControl";
 
 const useStyles = makeStyles({
   startButton: {
@@ -152,45 +153,50 @@ export function IOCService({ ioc, currentCommand, getCommands, buttonDisabled, s
 
   return (
     <>
-      <SimpleModal open={adHocDialogOpen} setOpen={setAdHocDialogOpen}>
-        <ConfirmationDialog open={adHocDialogOpen} setOpen={setAdHocDialogOpen} title={adHocDialogTitle} description={adHocDialogDescription} callback={callbackRef.current} />
-      </SimpleModal>
-
-      <Grid container spacing={1}>
-        {ioc.active === true ?
-          <Grid item xs={1} md={1}>
-            <Tooltip title={disabledStopButtonTitle}>
-              <span>
-                <Button className={classes.stopButton} onClick={openStopModal} disabled={buttonDisabled || ioc.operationInProgress}>Stop</Button>
-              </span>
-            </Tooltip>
-          </Grid>
-          :
-          <></>
-        }
-        {ioc.active === false ?
-          <Grid item xs={1} md={1}>
-            <Tooltip title={disabledStartButtonTitle}>
-              <span>
-                <Button className={classes.startButton} onClick={openStartModal} disabled={buttonDisabled || ioc.operationInProgress}>Start</Button>
-              </span>
-            </Tooltip>
-          </Grid>
-          :
-          <></>
-        }
-        <Grid item xs={12} md={12}>
-        {error ?
-          <Alert severity="error">{error}</Alert>
-          : 
-          <>
-            {command ?
-              <CommandJobStatus awxCommandId={command.awxCommandId} actionType={action} /> : inProgress ? <LinearProgress color="primary" /> : <></>
+      <AccessControl
+        allowedRoles={["DeploymentToolAdmin"]}
+        allowedUsersWithRoles={[{ user: ioc.owner, role: "DeploymentToolIntegrator" }]}
+        renderNoAccess={() => <></>}>
+        <SimpleModal open={adHocDialogOpen} setOpen={setAdHocDialogOpen}>
+          <ConfirmationDialog open={adHocDialogOpen} setOpen={setAdHocDialogOpen} title={adHocDialogTitle} description={adHocDialogDescription} callback={callbackRef.current} />
+        </SimpleModal>
+
+        <Grid container spacing={1}>
+          {ioc.active === true ?
+            <Grid item xs={1} md={1}>
+              <Tooltip title={disabledStopButtonTitle}>
+                <span>
+                  <Button className={classes.stopButton} onClick={openStopModal} disabled={buttonDisabled || ioc.operationInProgress}>Stop</Button>
+                </span>
+              </Tooltip>
+            </Grid>
+            :
+            <></>
+          }
+          {ioc.active === false ?
+            <Grid item xs={1} md={1}>
+              <Tooltip title={disabledStartButtonTitle}>
+                <span>
+                  <Button className={classes.startButton} onClick={openStartModal} disabled={buttonDisabled || ioc.operationInProgress}>Start</Button>
+                </span>
+              </Tooltip>
+            </Grid>
+            :
+            <></>
+          }
+          <Grid item xs={12} md={12}>
+            {error ?
+              <Alert severity="error">{error}</Alert>
+              :
+              <>
+                {command ?
+                  <CommandJobStatus awxCommandId={command.awxCommandId} actionType={action} /> : inProgress ? <LinearProgress color="primary" /> : <></>
+                }
+              </>
             }
-          </>
-        }
+          </Grid>
         </Grid>
-      </Grid>
+      </AccessControl>
     </>
   )
 }
diff --git a/src/components/deployments/DeploymentDetails.js b/src/components/deployments/DeploymentDetails.js
index 577cc86b..09dde3e1 100644
--- a/src/components/deployments/DeploymentDetails.js
+++ b/src/components/deployments/DeploymentDetails.js
@@ -10,6 +10,7 @@ import { Alert } from '@material-ui/lab';
 import { Link } from 'react-router-dom';
 import { formatDate } from '../common/Helper';
 import GitRefLink from '../IOC/GitRefLink';
+import AccessControl from '../auth/AccessControl';
 
 const useStyles = makeStyles({
   root: {
@@ -85,17 +86,20 @@ export function DeploymentDetails({ deployment, getDeployment }) {
       {alert && <Grid item xs={12}>
           {alert}
         </Grid>}
-      <Grid item xs={12} md={12} style={{paddingBottom: 0}}>
-        { deployment.awxJobId ?
-          <SimpleAccordion summary="Ansible Job Output" defaultExpanded>
-            <Container>
-              <DeploymentJobOutput awxJobId={deployment.awxJobId} update={updateOutput} />
-            </Container>
-          </SimpleAccordion>
-          :
-          <></>
-        }
-      </Grid>
+      <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]}
+        renderNoAccess={() => <></>}>
+        <Grid item xs={12} md={12} style={{ paddingBottom: 0 }}>
+          {deployment.awxJobId ?
+            <SimpleAccordion summary="Ansible Job Output" defaultExpanded>
+              <Container>
+                <DeploymentJobOutput awxJobId={deployment.awxJobId} update={updateOutput} />
+              </Container>
+            </SimpleAccordion>
+            :
+            <></>
+          }
+        </Grid>
+      </AccessControl>
     </Grid>
   );
 }
diff --git a/src/components/navigation/GlobalAppBar/GlobalAppBar.js b/src/components/navigation/GlobalAppBar/GlobalAppBar.js
index 78cc0d9b..f72f0fd3 100644
--- a/src/components/navigation/GlobalAppBar/GlobalAppBar.js
+++ b/src/components/navigation/GlobalAppBar/GlobalAppBar.js
@@ -397,6 +397,7 @@ export function GlobalAppBar({ children }) {
   const menuItemsVisitor = [
     makeLink("Explore IOCs", "/iocs", <CCCEControlSymbol />),
     makeLink("Explore IOC hosts", "/hosts", <Storage />),
+    makeLink("Deployment log", "/deployments", <Assignment />),
     makeLink("Statistics", "/statistics", <TrendingUp />),
     makeLink("About", "/about", <InfoIcon />)
   ]
diff --git a/src/views/IOC/IOCDetailsAccessControl.js b/src/views/IOC/IOCDetailsAccessControl.js
index 2c035c46..29d80d6c 100644
--- a/src/views/IOC/IOCDetailsAccessControl.js
+++ b/src/views/IOC/IOCDetailsAccessControl.js
@@ -12,7 +12,7 @@ export function IOCDetailsAccessControl({ match }) {
 
   return (
     <RootContainer>
-      <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]}>
+      <AccessControl allowedRoles={[]}>
         <IOCDetailsContainer id={id} />
       </AccessControl>
     </RootContainer>
diff --git a/src/views/deployments/DeploymentDetailsAccessControl.js b/src/views/deployments/DeploymentDetailsAccessControl.js
index 604ced75..062f1e45 100644
--- a/src/views/deployments/DeploymentDetailsAccessControl.js
+++ b/src/views/deployments/DeploymentDetailsAccessControl.js
@@ -10,7 +10,7 @@ export function DeploymentDetailsAccessControl({ match }) {
 
   return (
     <RootContainer>
-      <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]}>
+      <AccessControl allowedRoles={[]}>
         <DeploymentDetailsView id={id}/>
       </AccessControl>
     </RootContainer>
diff --git a/src/views/deployments/DeploymentsAccessControl.js b/src/views/deployments/DeploymentsAccessControl.js
index 17d3a9a7..dd1e9bb6 100644
--- a/src/views/deployments/DeploymentsAccessControl.js
+++ b/src/views/deployments/DeploymentsAccessControl.js
@@ -9,7 +9,7 @@ export function DeploymentsAccessControl() {
 
   return (
     <RootContainer>
-      <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]}>
+      <AccessControl allowedRoles={[]}>
         <DeploymentsView/>
       </AccessControl>
     </RootContainer>
diff --git a/src/views/deployments/DeploymentsView.js b/src/views/deployments/DeploymentsView.js
index 91ebaa7b..25b33cd0 100644
--- a/src/views/deployments/DeploymentsView.js
+++ b/src/views/deployments/DeploymentsView.js
@@ -9,6 +9,7 @@ import { useDeploymentSearch, userContext } from '../../api/SwaggerApi';
 import { initRequestParams } from '../../components/common/Helper';
 import { useEffect } from 'react';
 import { SearchBar } from '../../components/common/SearchBar/SearchBar';
+import AccessControl from '../../components/auth/AccessControl';
 
 const useStyles = makeStyles((theme) => ({
   root: {
@@ -130,10 +131,14 @@ export function DeploymentsView() {
           </Tabs>
         </Grid>
         <Grid item xs={8} md={2}>
-          <FormControlLabel className={classes.formControl}
-            control={<Switch checked={ownOnly} onChange={handleChangeOwn} />}
-            label={<Typography variant="h5">My deployments</Typography>}
-          />
+          <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]}
+            renderNoAccess={() => <></>} >
+
+            <FormControlLabel className={classes.formControl}
+              control={<Switch checked={ownOnly} onChange={handleChangeOwn} />}
+              label={<Typography variant="h5">My deployments</Typography>}
+            />
+          </AccessControl>
         </Grid>
         <Grid item xs={12} md={12}>
           {content}
diff --git a/src/views/host/HostDetailsAccessControl.js b/src/views/host/HostDetailsAccessControl.js
index d25c1500..ae00c37f 100644
--- a/src/views/host/HostDetailsAccessControl.js
+++ b/src/views/host/HostDetailsAccessControl.js
@@ -10,7 +10,7 @@ export function HostDetailsAccessControl({ match }) {
 
   return (
     <RootContainer data-testid="host-details-container">
-      <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]}>
+      <AccessControl allowedRoles={[]}>
         <HostDetailsView id={id}/>
       </AccessControl>
     </RootContainer>
diff --git a/src/views/host/HostDetailsView.js b/src/views/host/HostDetailsView.js
index 3ab8f4ba..8715f97e 100644
--- a/src/views/host/HostDetailsView.js
+++ b/src/views/host/HostDetailsView.js
@@ -20,6 +20,7 @@ import { KeyValueTable } from '../../components/common/KeyValueTable/KeyValueTab
 import { LokiPanel } from '../../components/common/Loki/LokiPanel';
 import { useHistory } from "react-router-dom";
 import { formatDate, initRequestParams } from '../../components/common/Helper';
+import AccessControl from '../../components/auth/AccessControl';
 
 const useStyles = makeStyles((theme) => ({
     secondItem: {
@@ -86,12 +87,15 @@ export function HostDetailsView({ id }) {
                         rowsPerPage={rowsPerPage}/>
                 </Hidden>
             </SimpleAccordion>
-            <SimpleAccordion summary="Syslog info" defaultExpanded>
-                {host &&
-                    <Container>
-                        <LokiPanel host={host} isSyslog={true} isDeployed={true} />
-                    </Container>}
-            </SimpleAccordion>
+            <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]}
+                renderNoAccess={() => <></>}>
+                <SimpleAccordion summary="Syslog info" defaultExpanded>
+                    {host &&
+                        <Container>
+                            <LokiPanel host={host} isSyslog={true} isDeployed={true} />
+                        </Container>}
+                </SimpleAccordion>
+            </AccessControl>
             <Card variant="outlined">
                 <CardContent>
                     <Container>
@@ -107,10 +111,13 @@ export function HostDetailsView({ id }) {
                     </Container>
                 </CardContent>
             </Card>
-            <SimpleAccordion summary="CSEntry Configuration">
+            <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]}
+                renderNoAccess={() => <></>} >
+                <SimpleAccordion summary="CSEntry Configuration">
 
-                {host && <KeyValueTable obj={renderHost} variant="table" />}
-            </SimpleAccordion>
+                    {host && <KeyValueTable obj={renderHost} variant="table" />}
+                </SimpleAccordion>
+            </AccessControl>
         </Paper>
     );
 }
\ No newline at end of file
-- 
GitLab