Merge pull request #3729 from appwrite/fix-permissions-ui

Fix + improve permissions UI
This commit is contained in:
Christy Jacob 2022-08-30 15:01:02 +02:00 committed by GitHub
commit faaf233153
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 67 additions and 41 deletions

View file

@ -294,6 +294,7 @@ App::get('/console/databases/collection')
$permissions
->setParam('method', 'databases.getCollection')
->setParam('events', 'load,databases.updateCollection')
->setParam('form', 'collectionPermissions')
->setParam('data', 'project-collection')
->setParam('params', [
'collection-id' => '{{router.params.id}}',
@ -329,7 +330,6 @@ App::get('/console/databases/document')
->action(function (string $databaseId, string $collection, View $layout) {
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
$logs
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
->setParam('method', 'databases.listDocumentLogs')
@ -341,15 +341,16 @@ App::get('/console/databases/document')
;
$permissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
$permissions
->setParam('method', 'databases.getDocument')
->setParam('events', 'load,databases.updateDocument')
->setParam('form', 'documentPermissions')
->setParam('data', 'project-document')
->setParam('permissions', \array_filter(
Database::PERMISSIONS,
fn ($perm) => $perm != Database::PERMISSION_CREATE
))
->setParam('permissions', [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
])
->setParam('params', [
'collection-id' => '{{router.params.collection}}',
'database-id' => '{{router.params.databaseId}}',
@ -384,10 +385,12 @@ App::get('/console/databases/document/new')
$permissions
->setParam('data', 'project-document')
->setParam('permissions', \array_filter(
Database::PERMISSIONS,
fn ($perm) => $perm != Database::PERMISSION_CREATE
))
->setParam('form', 'documentPermissions')
->setParam('permissions', [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
])
->setParam('params', [
'collection-id' => '{{router.params.collection}}',
'database-id' => '{{router.params.databaseId}}',
@ -451,20 +454,22 @@ App::get('/console/storage/bucket')
$fileCreatePermissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
$fileCreatePermissions
->setParam('form', 'fileCreatePermissions')
->setParam('permissions', \array_filter(
Database::PERMISSIONS,
fn ($perm) => $perm != Database::PERMISSION_CREATE
));
->setParam('permissions', [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
]);
$fileUpdatePermissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
$fileUpdatePermissions
->setParam('method', 'storage.getFile')
->setParam('data', 'file')
->setParam('form', 'fileUpdatePermissions')
->setParam('permissions', \array_filter(
Database::PERMISSIONS,
fn ($perm) => $perm != Database::PERMISSION_CREATE
))
->setParam('permissions', [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
])
->setParam('params', [
'bucket-id' => '{{router.params.id}}',
]);

View file

@ -7,7 +7,7 @@ $params = $this->getParam('params', []);
$events = $this->getParam('events', '');
$permissions = $this->getParam('permissions', Database::PERMISSIONS);
$data = $this->getParam('data', '');
$form = $this->getParam('form', 'form');
$form = $this->getParam('form');
$escapedPermissions = \array_map(function ($perm) {
// Alpine won't bind to a parameter named delete
@ -34,7 +34,7 @@ $escapedPermissions = \array_map(function ($perm) {
<?php if (!empty($data)): ?>
data-name="<?php echo $data; ?>"
<?php endif; ?>
@reset.window="permissions = rawPermissions = []">
@reset.window="permissions.length = rawPermissions.length = 0">
<input
type="hidden"
@ -79,7 +79,7 @@ $escapedPermissions = \array_map(function ($perm) {
</tr>
</template>
<tr x-data="permissionsRow"
@addrow.window="addPermission('<?php echo $form; ?>',role,{<?php echo \implode(',', $escapedPermissions) ?>})">
@addrow<?php echo \strtolower($form); ?>.window="addPermission('<?php echo $form; ?>', role, { <?php echo \implode(',', $escapedPermissions) ?> })">
<td>
<datalist id="types">
<option value="user:">
@ -91,7 +91,7 @@ $escapedPermissions = \array_map(function ($perm) {
<input
required
id="<?php echo $form; ?>"
id="<?php echo $form; ?>Input"
name="<?php echo $form; ?>"
form="<?php echo $form ?>"
list="types"
@ -109,7 +109,7 @@ $escapedPermissions = \array_map(function ($perm) {
<tfoot>
<tr>
<td colspan="<?php \count($permissions) + 2 ?>">
<button type="button" class="btn btn-primary margin-top-small" @click="$dispatch('addrow')">Add</button>
<button type="button" class="margin-top-small reverse" @click="$dispatch('addrow<?php echo \strtolower($form); ?>')">Add</button>
</td>
</tr>
</tfoot>

View file

@ -514,7 +514,7 @@ $permissions = $this->getParam('permissions', null);
<div class="row responsive margin-top-negative">
<div class="col span-8 margin-bottom">
<form id="<?php echo $permissions->getParam('form', 'permissions') ?>"></form>
<form id="<?php echo $permissions->getParam('form') ?>"></form>
<form
data-analytics

View file

@ -53,7 +53,7 @@ $permissions = $this->getParam('permissions', null);
<div class="row responsive">
<div class="col span-8 margin-bottom">
<form id="<?php echo $permissions->getParam('form', 'permissions') ?>"></form>
<form id="<?php echo $permissions->getParam('form') ?>"></form>
<form
data-analytics

View file

@ -108,7 +108,7 @@ $fileUpdatePermissions = $this->getParam('fileUpdatePermissions', null);
<td class="col-name" data-title="Name: " data-ls-attrs="title={{file.name}}" >
<div class="trim-inner-text">
<span data-ls-ui-trigger="modal-file-update-{{file.$id}}" class="link text-one-liner" data-ls-bind="{{file.name}}"></span>
<div data-ls-attrs="data-open-event=modal-file-update-{{file.$id}}" data-button-hide="1" data-ui-modal class="box modal sticky-footer width-large close" >
<div data-ls-attrs="data-open-event=modal-file-update-{{file.$id}}" data-button-hide="1" data-ui-modal class="box modal width-xlarge sticky-footer close" >
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Update File</h1>
@ -139,12 +139,11 @@ $fileUpdatePermissions = $this->getParam('fileUpdatePermissions', null);
</div>
<input type="hidden" data-ls-attrs="id=file-bucketId-{{file.$id}}" name="bucketId" data-ls-bind="{{file.bucketId}}">
<div class="toggle margin-bottom" data-ls-if="{{project-bucket.fileSecurity}}" data-ls-ui-open data-button-aria="Open Permissions">
<i class="icon-plus pull-end margin-top-tiny"></i>
<i class="icon-minus pull-end margin-top-tiny"></i>
<hr class="margin-start margin-end" />
<h3 class="margin-bottom-large">Permissions</h3>
<h3 class="margin-bottom">Permissions</h3>
<div class="margin-start margin-end margin-bottom">
<?php echo $fileUpdatePermissions->render(); ?>
</div>
</form>

2
composer.lock generated
View file

@ -5391,5 +5391,5 @@
"platform-overrides": {
"php": "8.0"
},
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.2.0"
}

View file

@ -4054,11 +4054,13 @@ if(this.attribute){event+=`.${this.attribute}`;}
this.events.add(event);this.reset();},removeEvent(value){this.events.delete(value);}}));});})(window);(function(window){document.addEventListener('alpine:init',()=>{Alpine.data('permissionsMatrix',()=>({permissions:[],rawPermissions:[],load(permissions){if(permissions===undefined){return;}
this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePermission(p);type=this.parseInputPermission(type);let index=-1;let existing=this.permissions.find((p,idx)=>{if(p.role===role){index=idx;return true;}})
if(existing===undefined){this.permissions.push({role,[type]:true,});}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!document.getElementById(formId).reportValidity()){return;}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!this.validate(formId,role,permissions)){return;}
Object.entries(permissions).forEach(entry=>{let[type,enabled]=entry;type=this.parseOutputPermission(type);if(enabled){this.rawPermissions.push(this.buildPermission(type,role));}});this.permissions.push({role,...permissions,});this.reset();},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';}
return key;}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
return key;},validate(formId,role,permissions){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input`);input.setCustomValidity('');if(!Object.values(permissions).some(p=>p)){input.setCustomValidity('No permissions selected');}
if(this.permissions.some(p=>p.role===role)){input.setCustomValidity('Role entry already exists');}
return form.reportValidity();}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
throw new Error("This callback is only valid for forms");};},alert:function(text,classname){return function(alerts){alerts.add({text:text,class:classname||"success"},6000);};},redirect:function(url){return function(router){if(url==="/console"){window.location=url;return;}
router.change(url||"/");};},reload:function(){return function(router){router.reload();};},state:function(keys){let updateQueryString=function(key,value,url){var re=new RegExp("([?&])"+key+"=.*?(&|#|$)(.*)","gi"),hash;if(re.test(url)){if(typeof value!=="undefined"&&value!==null){return url.replace(re,"$1"+key+"="+value+"$2$3");}else{hash=url.split("#");url=hash[0].replace(re,"$1$3").replace(/(&|\?)$/,"");if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}
return url;}}else{if(typeof value!=="undefined"&&value!==null){var separator=url.indexOf("?")!==-1?"&":"?";hash=url.split("#");url=hash[0]+separator+key+"="+value;if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}

View file

@ -609,11 +609,13 @@ if(this.attribute){event+=`.${this.attribute}`;}
this.events.add(event);this.reset();},removeEvent(value){this.events.delete(value);}}));});})(window);(function(window){document.addEventListener('alpine:init',()=>{Alpine.data('permissionsMatrix',()=>({permissions:[],rawPermissions:[],load(permissions){if(permissions===undefined){return;}
this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePermission(p);type=this.parseInputPermission(type);let index=-1;let existing=this.permissions.find((p,idx)=>{if(p.role===role){index=idx;return true;}})
if(existing===undefined){this.permissions.push({role,[type]:true,});}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!document.getElementById(formId).reportValidity()){return;}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!this.validate(formId,role,permissions)){return;}
Object.entries(permissions).forEach(entry=>{let[type,enabled]=entry;type=this.parseOutputPermission(type);if(enabled){this.rawPermissions.push(this.buildPermission(type,role));}});this.permissions.push({role,...permissions,});this.reset();},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';}
return key;}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
return key;},validate(formId,role,permissions){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input`);input.setCustomValidity('');if(!Object.values(permissions).some(p=>p)){input.setCustomValidity('No permissions selected');}
if(this.permissions.some(p=>p.role===role)){input.setCustomValidity('Role entry already exists');}
return form.reportValidity();}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
throw new Error("This callback is only valid for forms");};},alert:function(text,classname){return function(alerts){alerts.add({text:text,class:classname||"success"},6000);};},redirect:function(url){return function(router){if(url==="/console"){window.location=url;return;}
router.change(url||"/");};},reload:function(){return function(router){router.reload();};},state:function(keys){let updateQueryString=function(key,value,url){var re=new RegExp("([?&])"+key+"=.*?(&|#|$)(.*)","gi"),hash;if(re.test(url)){if(typeof value!=="undefined"&&value!==null){return url.replace(re,"$1"+key+"="+value+"$2$3");}else{hash=url.split("#");url=hash[0].replace(re,"$1$3").replace(/(&|\?)$/,"");if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}
return url;}}else{if(typeof value!=="undefined"&&value!==null){var separator=url.indexOf("?")!==-1?"&":"?";hash=url.split("#");url=hash[0]+separator+key+"="+value;if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -34,7 +34,7 @@
});
},
addPermission(formId, role, permissions) {
if (!document.getElementById(formId).reportValidity()) {
if (!this.validate(formId, role, permissions)) {
return;
}
Object.entries(permissions).forEach(entry => {
@ -105,6 +105,21 @@
return 'delete';
}
return key;
},
validate(formId, role, permissions) {
const form = document.getElementById(formId);
const input = document.getElementById(`${formId}Input`);
input.setCustomValidity('');
if (!Object.values(permissions).some(p => p)) {
input.setCustomValidity('No permissions selected');
}
if (this.permissions.some(p => p.role === role)) {
input.setCustomValidity('Role entry already exists');
}
return form.reportValidity();
}
}));
Alpine.data('permissionsRow', () => ({

View file

@ -89,6 +89,10 @@
max-width: 800px;
}
&.width-xlarge {
max-width: 950px;
}
&.open {
display: block;
}

View file

@ -1,13 +1,12 @@
.permissions-matrix {
th:first-child, td:first-child {
width: 100px;
width: 35%;
}
th:not(:first-child):not(:last-child), td:not(:first-child):not(:last-child) {
width: 50px;
text-align: center;
}
th:last-child, td:last-child {
width: 20px;
width: 10%;
}
td {
vertical-align: middle;