Added frond-end and needed get calls.

This commit is contained in:
Naida Vatric
2019-11-07 01:10:25 +01:00
parent 0d525d486e
commit 54aad28c5f
30 changed files with 14267 additions and 2 deletions

View File

@@ -0,0 +1,26 @@
//Call for listing all groups with their permissions - needed for control panel
//Groups without permissions also included
//
const handleListPerm = (req, res, db) => {
db.transaction ( trx => {
trx.select('groups.groupname', 'permissions.objname', 'permissions.type')
.from('groups')
.leftJoin('permissions', function () {
this.on('groups.groupname', 'ilike','permissions.owner')
})
.where('username', 'like', '')
.orderBy('groupname', 'asc')
.orderBy('objname', 'asc')
.then ( data => {
res.json(data);
})
.then(trx.commit)
.catch(trx.rollback)
.catch (err => res.status(400).json('Error accesing database.'))
})
}
module.exports = {
handleListPerm
}

View File

@@ -0,0 +1,20 @@
//Call for listing all groups with their users - needed for control panel
//
const handleListGroup = (req, res, db) => {
db.transaction (trx => {
trx.select('*')
.from('groups')
.orderBy('groupname', 'asc')
.orderBy('username', 'asc')
.then( data => {
res.json(data);
})
.then(trx.commit)
.catch(trx.rollback)
.catch (err => res.status(400).json('Error accesing database.'))
})
}
module.exports = {
handleListGroup
}

View File

@@ -0,0 +1,29 @@
//Call for listing all users with their permissions - needed for control panel
//Users without permissions also included
//
const handleListPerm = (req, res, db) => {
db.transaction ( trx => {
trx.select('users.username', 'permissions.objname', 'permissions.type')
.from('groups')
.fullOuterJoin('permissions', function () {
this.on('groups.groupname', '=', 'permissions.owner')
})
.rightJoin('users', function () {
this.on('groups.username', 'ilike','users.username')
.orOn('permissions.owner', 'ilike', 'users.username')
})
.orderBy('username', 'asc')
.orderBy('objname', 'asc')
.then ( data => {
res.json(data);
})
.then(trx.commit)
.catch(trx.rollback)
.catch (err => res.status(400).json('Error accesing database.'))
})
}
module.exports = {
handleListPerm
}

View File

@@ -27,6 +27,9 @@ const addpermission= require('./endpoints/addpermission');
const clearpermissions= require('./endpoints/clearpermissions');
const testuserperm= require('./endpoints/testuserperm');
const querypermiss= require('./endpoints/querypermiss');
const listgroupusers= require('./endpoints/listgroupusers');
const listgrouppermissions= require('./endpoints/listgrouppermissions');
const listuserpermissions= require('./endpoints/listuserpermissions');
//Requiremenst for input data validation
const { addUserValidationRules, addPermissionValidationRules, validate, } = require('./validators/validator')
@@ -35,6 +38,19 @@ app.get('/', (req, res) => {
res.send("Work in progress...");
})
//Call for listing all groups with their users - needed for control panel
app.get('/listgroupusers', (req, res) => {
listgroupusers.handleListGroup(req, res, db);
});
//Call for listing all permissions with their owners groups and users - needed for control panel
app.get('/listgrouppermissions', (req, res) => {
listgrouppermissions.handleListPerm(req, res, db);
});
app.get('/listuserpermissions', (req, res) => {
listuserpermissions.handleListPerm(req, res, db);
});
//Call for adding a user to a group
app.post('/addusertogroup', addUserValidationRules(), validate, (req, res) => {
addusertogroup.handleAddUserToGroup(req, res, db)});
@@ -69,5 +85,3 @@ app.listen(3000, () => {
console.log(`App is running on port 3000`);
})
//For testing purpose
//module.exports = app;

View File

@@ -0,0 +1 @@
PORT=8000

23
permissions_control_panel/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -0,0 +1,42 @@
# Naida test
***Permissions Control Panel
Permissions API is front-end webpage for controlling User Permissions.
*Getting Started
Instructions for running webpage on your local machine for develoment and testing purposes.
*Prerequisited
Pull project folder from GitLab to your local machine: https://gitlab.com/saburly/testovi/naida
Front-end webpage files are in folder /permissions_control_panel.
*Installing dependencies
Check that package.json has listed "dependencies": {
"react": "^16.11.0",
"react-dom": "^16.11.0",
"react-scripts": "3.2.0",
"tachyons": "^4.11.1"
}
In terminal navigate to folder /permissions_control_panel and run:
$npm install
This should install all needed dependencies (node modules).
*Start project
As API is running on default localport 3000, for testing and development purposes, front-end webpage will be running on port 8000 as defined in env file.
To run both front end and back-end, open terminal, cd into root project folder and run script
$chmod +x startall.sh
$sh startall.sh
*Built With
-React
-Tachyons

13321
permissions_control_panel/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
{
"name": "permissions_control_panel",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.11.0",
"react-dom": "^16.11.0",
"react-scripts": "3.2.0",
"tachyons": "^4.11.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Permissions Control Panel</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,2 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *

View File

@@ -0,0 +1,23 @@
.App {
text-align: center;
background-color: #282c34;
}
.App-logo {
height: 40vmin;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #09d3ac;
}

View File

@@ -0,0 +1,162 @@
import React, {Component} from 'react';
import './App.css';
//Required components
import AddUser from './components/adduser/AddUser';
import AddPermission from './components/addpermission/AddPermission';
import ClearGroup from './components/cleargroup/ClearGroup';
import ClearPermission from './components/clearpermission/ClearPermission';
import GroupList from './components/grouplist/GroupList';
import PermissionList from './components/permissionlist/PermissionList';
class App extends Component {
constructor () {
super();
this.state = {
changeCount: 0,
groupselected: '',
ownerselected: '',
ownertype: ''
};
}
//Function that changes key for rerendering of lists after adding-clearing
onChange = () => {
let tmp=this.state.changeCount;
tmp++;
this.setState( {
changeCount: tmp
});
}
//Function that adds user to group in database after button click
onAddUser = (event) => {
let user= prompt("Username: ");
let group=prompt("Group: ");
fetch ( 'http://localhost:3000/addusertogroup', {
method: 'post',
headers: { 'Content-type': 'application/json'},
body: JSON.stringify({
requser: user,
reqgroup: group
})
})
.then(response => response.json())
.then(user => {
if (user.id) {
alert("User added to group.");
this.onChange();
} else {
alert("Could not add user to group.");
}
})
.catch(err => console.log('Error:', err))
}
//Function that adds permission to group or user in database after button click
onAddPermission = (event) => {
let ownertype= prompt("Permission Owner Type (user or group): ");
let owner=prompt("Permission owner: ");
let object = prompt("Object name: ");
let type = prompt("Permission type: ");
fetch ( 'http://localhost:3000/addpermission', {
method: 'post',
headers: { 'Content-type': 'application/json'},
body: JSON.stringify({
reqowner: owner,
reqownertype: ownertype,
reqobjname: object,
reqtype: type
})
})
.then(response => response.json())
.then(perm => {
if (perm.id) {
alert("Permision added.");
this.onChange();
} else {
alert("Could not add permission.");
}
})
.catch(err => console.log('Error:', err))
}
//Function that clears all users from group in database after button click
onClearGroup = (event) => {
let group=this.state.groupselected;
fetch ( 'http://localhost:3000/cleargroup', {
method: 'post',
headers: { 'Content-type': 'application/json'},
body: JSON.stringify({
reqgroup: group
})
})
.then(response => response.json())
.then(mess => {
if (mess.includes('cleared')) {
alert("Group cleared.");
this.onChange();
} else {
alert("Could not clear group.");
}
})
.catch(err => console.log('Error:', err))
}
//Function that clears all permissions from owner in database after button click
onClearPerm = (event) => {
let ownertype=this.state.ownertype;
let owner=this.state.ownerselected;
fetch ( 'http://localhost:3000/clearpermissions', {
method: 'post',
headers: { 'Content-type': 'application/json'},
body: JSON.stringify({
reqowner: owner,
reqownertype: ownertype
})
})
.then(response => response.json())
.then(mess => {
if (mess.includes('cleared')) {
alert("Permissions cleared.");
this.onChange();
} else {
alert("Could not clear permissions.");
}
})
.catch(err => console.log('Error:', err))
}
//Function that selects group for clear after it is selected in list
selectGroup = (group) => {
this.setState( {groupselected: group});
}
//Function that selects permission owner for clear after it is selected in list
selectPerm = (owner, type) => {
this.setState( {
ownerselected: owner,
ownertype: type
});
}
render() {
return (
<div className="App">
<div className="di flex justify-around">
<GroupList key={this.state.changeCount} selectGroup={this.selectGroup}/>
<PermissionList key={this.state.changeCount+1} selectPerm={this.selectPerm}/>
</div>
<div>
<AddUser onAddUser={this.onAddUser}/>
<AddPermission onAddPermission={this.onAddPermission}/>
<ClearGroup onClearGroup={this.onClearGroup}/>
<ClearPermission onClearPerm={this.onClearPerm}/>
</div>
</div>
);
}
}
export default App;

View File

@@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

View File

@@ -0,0 +1,12 @@
import React from 'react';
const AddPermission= ({onAddPermission}) => {
return (
<div className='ph3 dib'>
<a className="f6 link dim ph3 pv2 mb2 dib white bg-light-purple" href="#0"
onClick={()=> onAddPermission()}>Add Permission</a>
</div>
);
}
export default AddPermission;

View File

@@ -0,0 +1,12 @@
import React from 'react';
const AddUser = ({onAddUser}) => {
return (
<div className='ph3 dib'>
<a className="f6 link dim ph3 pv2 mb2 dib white bg-purple" href="#0"
onClick={()=> onAddUser()}>Add User To Group</a>
</div>
);
}
export default AddUser;

View File

@@ -0,0 +1,12 @@
import React from 'react';
const ClearGroup = ({onClearGroup}) => {
return (
<div className='ph3 dib'>
<a className="f6 link dim ph3 pv2 mb2 dib white bg-hot-pink" href="#0"
onClick={()=>onClearGroup()}>Clear Group</a>
</div>
);
}
export default ClearGroup;

View File

@@ -0,0 +1,12 @@
import React from 'react';
const ClearPermission =({onClearPerm}) => {
return (
<div className='ph3 dib'>
<a className="f6 link dim ph3 pv2 mb2 dib white bg-dark-pink" href="#0"
onClick={()=>onClearPerm()}>Clear Permissions</a>
</div>
);
}
export default ClearPermission;

View File

@@ -0,0 +1,17 @@
s {
text-decoration: none;
}
.whbg {
background: white;
}
.wht {
color: white;
}
.scroll {
max-height:150px;
overflow:auto;
}
.not-und {
text-decoration: none;
}

View File

@@ -0,0 +1,76 @@
import React, {Component} from 'react';
import './GroupList.css';
class GroupList extends Component {
constructor (props) {
super (props);
this.state = {
groups: []
}
}
componentDidMount () {
fetch('http://localhost:3000/listgroupusers')
.then (response => response.json())
.then( response => {
//Changing received data to suitable form
let group=[
{
groupname: response[0].groupname,
users: [response[0].username]
}
];
let j=0;
for (let i=1; i<response.length; i++) {
if(response[i].groupname.toLowerCase()===response[i-1].groupname.toLowerCase()) {
group[j].users.push(response[i].username);
}
else {
group.push({
groupname: response[i].groupname,
users: [response[i].username]
});
j++;
}
}
this.setState( {groups: group});
})
.catch (err => console.log(err));
}
//Function that changes received data to list items
makeItems = (group, selectGroup) => {
return (
<ul className="list ph3 ph5-ns pv4 whbg scroll">
{group.map( (item, index) =>
<li key={index} className="db mr2">
<a href="#0" key={index*10}
onClick={()=> selectGroup(item.groupname)}
className="f6 f5-ns b db pa2 link dim mid-gray not-und">
<s key={index*100}>{item.groupname}</s>
{item.users.map( (user, index2) => {
if(user.length!==0) {
return <s key={index2}>, {user}</s>
}
else {
return '';
}
}
)}
</a>
</li>
)}
</ul>
);
}
render () {
const {selectGroup} = this.props;
return (
<div className="dib">
<h3 className="wht">List of Groups with all Users</h3>
{this.makeItems(this.state.groups, selectGroup)}
</div>
);
}
}
export default GroupList;

View File

@@ -0,0 +1,13 @@
s {
text-decoration: none;
}
.whbg {
background: white;
}
.wht {
color: white;
}
.scroll {
max-height:150px;
overflow:auto;
}

View File

@@ -0,0 +1,172 @@
import React, {Component} from 'react';
import './PermissionList.css';
class PermissionList extends Component {
constructor (props) {
super (props);
this.state = {
groupswperm: [],
userswperm: []
}
}
componentDidMount () {
//Getting data for groups and all associated permissions
fetch('http://localhost:3000/listgrouppermissions')
.then (response => response.json())
.then( response => {
let groups=[
{
groupname: response[0].groupname,
permissions: [
{ object: response[0].objname,
type: response[0].type
}
]
}
];
let j=0;
for (let i=1; i<response.length; i++) {
if(response[i].groupname.toLowerCase()===response[i-1].groupname.toLowerCase()) {
if(response[i].objname !==null) {
groups[j].permissions.push(
{ object: response[i].objname,
type: response[i].type
}
);
}
}
else {
if (response[i].objname !==null) {
groups.push({
groupname: response[i].groupname,
permissions: [
{ object: response[i].objname,
type: response[i].type
}
]
});
j++;
}
else if (response[i].objname ===null) {
groups.push({
groupname: response[i].groupname,
permissions: []
});
j++;
}
}
}
this.setState( {groupswperm: groups});
})
.catch (err => console.log(err));
//Getting data for users and all associated permissions
fetch('http://localhost:3000/listuserpermissions')
.then (response => response.json())
.then( response => {
let users=[
{
username: response[0].username,
permissions: [
{ object: response[0].objname,
type: response[0].type
}
]
}
];
let j=0;
for (let i=1; i<response.length; i++) {
if(response[i].username===response[i-1].username) {
if(response[i].objname !==null) {
users[j].permissions.push(
{ object: response[i].objname,
type: response[i].type
}
);
}
}
else {
if (response[i].objname !==null) {
users.push({
username: response[i].username,
permissions: [
{ object: response[i].objname,
type: response[i].type
}
]
});
j++;
}
else if (response[i].objname ===null) {
users.push({
username: response[i].username,
permissions: []
});
j++;
}
}
}
this.setState( {userswperm: users});
})
.catch (err => console.log(err));
}
//Function that changes received data to list items
makeItems = (groups, users, selectPerm) => {
return (
<ul className="list ph3 ph5-ns pv4 whbg scroll">
{groups.map( (item, index) =>
<li key={index} className="db mr2">
<a href="#0" key={index*10}
onClick ={()=> selectPerm(item.groupname, 'group')}
className="f6 f5-ns b db pa2 link dim mid-gray not-und">
<s key={index*100}>{item.groupname} (G)</s>
{item.permissions.map( (perm, index2) => {
if(perm.length!==0 && perm.object!==null) {
return <s key={index2}>, {perm.object} - {perm.type}</s>
}
else {
return '';
}
}
)}
</a>
</li>
)}
{users.map( (item, index) =>
<li key={index*2} className="db mr2">
<a href="#0" key={index*20}
onClick ={()=> selectPerm(item.username, 'user')}
className="f6 f5-ns b db pa2 link dim mid-gray not-und">
<s key={index*200}>{item.username} (U)</s>
{item.permissions.map( (perm, index2) => {
if(perm.length!==0 && perm.object!==null) {
return <s key={index2*2}>, {perm.object} - {perm.type}</s>
}
else {
return '';
}
}
)}
</a>
</li>
)}
</ul>
);
}
render () {
const {selectPerm} = this.props;
return (
<div className="dib">
<h3 className="wht">List of all Permissions</h3>
{this.makeItems(this.state.groupswperm, this.state.userswperm, selectPerm)}
</div>
);
}
}
export default PermissionList;

View File

@@ -0,0 +1,15 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #282c34;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}

View File

@@ -0,0 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import 'tachyons';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

View File

@@ -0,0 +1,135 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

4
startall.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
npm start &
cd permissions_control_panel
npm start &