Begin implementing permissions page

This commit is contained in:
Toby Zerner 2015-07-29 21:00:27 +09:30
parent 5f1db93e3d
commit 6873f77012
8 changed files with 431 additions and 4 deletions

View File

@ -0,0 +1,269 @@
import Component from 'flarum/Component';
import Badge from 'flarum/components/Badge';
import Select from 'flarum/components/Select';
import Button from 'flarum/components/Button';
import Group from 'flarum/models/Group';
import icon from 'flarum/helpers/icon';
import ItemList from 'flarum/utils/ItemList';
export default class PermissionsPage extends Component {
constructor(...args) {
super(...args);
this.groups = app.store.all('groups')
.filter(group => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(Number(group.id())) === -1);
this.permissions = this.permissionItems().toArray();
this.scopes = this.scopeItems().toArray();
this.scopeControls = this.scopeControlItems().toArray();
}
view() {
const permissionCells = permission => {
return this.scopes.map(scope => (
<td>
{scope.render(permission)}
</td>
));
};
return (
<div className="PermissionsPage">
<div className="PermissionsPage-groups">
<div className="container">
{this.groups.map(group => (
<button className="Button Group">
{Badge.component({
className: 'Group-icon',
icon: group.icon(),
style: {backgroundColor: group.color()}
})}
<span className="Group-name">{group.namePlural()}</span>
</button>
))}
<button className="Button Group Group--add">
{icon('plus', {className: 'Group-icon'})}
<span className="Group-name">New Group</span>
</button>
</div>
</div>
<div className="PermissionsPage-permissions">
<div className="container">
<table className="PermissionGrid">
<thead>
<tr>
<td></td>
{this.scopes.map(scope => <th>{scope.label}</th>)}
<th>{this.scopeControls}</th>
</tr>
</thead>
{this.permissions.map(section => (
<tbody>
<tr className="PermissionGrid-section">
<th>{section.label}</th>
{permissionCells(section)}
<td/>
</tr>
{section.children.map(child => (
<tr className="PermissionGrid-child">
<th>{child.label}</th>
{permissionCells(child)}
<td/>
</tr>
))}
</tbody>
))}
</table>
</div>
</div>
</div>
);
}
permissionItems() {
const items = new ItemList();
items.add('view', {
label: 'View the forum',
children: this.viewItems().toArray()
});
items.add('start', {
label: 'Start discussions',
children: this.startItems().toArray()
});
items.add('reply', {
label: 'Reply to discussions',
children: this.replyItems().toArray()
});
items.add('moderate', {
label: 'Moderate',
children: this.moderateItems().toArray()
});
return items;
}
viewItems() {
const items = new ItemList();
items.add('view', {
label: 'View discussions',
permission: 'forum.view',
allowGuest: true
});
items.add('signUp', {
label: 'Sign up',
setting: Select.component({options: ['Open']})
});
return items;
}
startItems() {
const items = new ItemList();
items.add('start', {
label: 'Start discussions',
permission: 'forum.startDiscussion'
});
items.add('allowRenaming', {
label: 'Allow renaming',
setting: Select.component({options: ['Indefinitely']})
});
return items;
}
replyItems() {
const items = new ItemList();
items.add('reply', {
label: 'Reply to discussions',
permission: 'discussion.reply'
});
items.add('allowPostEditing', {
label: 'Allow post editing',
setting: Select.component({options: ['Indefinitely']})
});
return items;
}
moderateItems() {
const items = new ItemList();
items.add('editPosts', {
label: 'Edit posts',
permission: 'discussion.editPosts'
});
items.add('deletePosts', {
label: 'Delete posts',
permission: 'discussion.deletePosts'
});
items.add('renameDiscussions', {
label: 'Rename discussions',
permission: 'discussion.rename'
});
items.add('deleteDiscussions', {
label: 'Delete discussions',
permission: 'discussion.delete'
});
items.add('suspendUsers', {
label: 'Suspend users',
permission: 'user.suspend'
});
return items;
}
scopeItems() {
const items = new ItemList();
const groupBadge = id => {
const group = app.store.getById('groups', id);
return Badge.component({
icon: group.icon(),
style: {backgroundColor: group.color()},
label: group.namePlural()
});
};
const groupBadges = groupIds => {
let content;
if (groupIds.indexOf(String(Group.GUEST_ID)) !== -1) {
content = 'Everyone';
} else if (groupIds.indexOf(String(Group.MEMBER_ID)) !== -1) {
content = 'Members';
} else {
content = [
groupBadge(Group.ADMINISTRATOR_ID),
groupIds.map(groupBadge)
];
}
return (
<button className="Button Button--text">
{content}
{icon('sort', {className: 'GroupsButton-caret'})}
</button>
);
};
items.add('global', {
label: 'Global',
render: permission => {
if (permission.setting) {
return permission.setting;
} else if (permission.permission) {
const groupIds = app.forum.attribute('permissions')[permission.permission] || [];
return groupBadges(groupIds);
}
return '';
}
});
items.add('tag1', {
label: 'Blog',
render: permission => {
if (permission.setting) {
return '';
} else if (permission.permission) {
const groupIds = app.forum.attribute('permissions')[permission.permission] || [];
return groupBadges(groupIds);
}
return '';
}
});
return items;
}
scopeControlItems() {
const items = new ItemList();
items.add('addTag', Button.component({
children: 'Restrict by Tag',
icon: 'plus',
className: 'Button Button--text'
}))
return items;
}
}

View File

@ -38,6 +38,6 @@ export default class Badge extends Component {
config(isInitialized) { config(isInitialized) {
if (isInitialized) return; if (isInitialized) return;
this.$().tooltip(); if (this.props.label) this.$().tooltip();
} }
} }

View File

@ -15,7 +15,7 @@ export default class Select extends Component {
return ( return (
<span className="Select"> <span className="Select">
<select className="Select-input FormControl" onchange={m.withAttr('value', onchange.bind(this))} value={value}> <select className="Select-input FormControl" onchange={onchange ? m.withAttr('value', onchange.bind(this)) : undefined} value={value}>
{Object.keys(options).map(key => <option value={key}>{options[key]}</option>)} {Object.keys(options).map(key => <option value={key}>{options[key]}</option>)}
</select> </select>
{icon('sort', {className: 'Select-caret'})} {icon('sort', {className: 'Select-caret'})}

View File

@ -0,0 +1,86 @@
.PermissionsPage-groups {
background: @control-bg;
padding: 30px 0;
}
.Group {
width: 90px;
display: inline-block;
text-align: center;
color: @text-color;
font-weight: bold;
}
.Group-name {
display: block;
margin-top: 5px;
}
.Group-icon {
font-size: 14px;
}
.Group--add {
border: 1px dashed @muted-color;
color: @muted-color;
width: auto;
margin-left: 10px;
font-weight: normal;
}
.PermissionsPage-permissions {
padding: 30px 0;
}
.PermissionGrid {
td, th {
padding: 10px 0;
text-align: left;
}
td {
color: @muted-color;
}
thead th {
text-transform: uppercase;
font-weight: bold;
font-size: 12px;
color: @muted-color;
width: 140px;
}
tbody {
th {
padding-right: 50px;
}
tr:last-child {
td, th {
padding-bottom: 15px !important;
}
}
select {
background: transparent;
padding: 0 30px 0 0;
height: auto;
border: 0;
width: 100%;
}
.Button {
text-decoration: none;
}
td:not(:hover) {
.Select-caret, .GroupsButton-caret {
display: none;
}
}
}
}
.PermissionGrid-section {
td, th {
padding-top: 15px;
border-top: 1px solid @control-bg;
}
}
.PermissionGrid-child {
td, th {
padding: 5px 0;
}
th {
font-weight: normal;
}
}

View File

@ -156,7 +156,7 @@
.Button--text { .Button--text {
background: transparent !important; background: transparent !important;
padding: 0; padding: 0;
color: @muted-color !important; color: inherit !important;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;

View File

@ -13,11 +13,18 @@ class GroupSerializer extends Serializer
protected function getDefaultAttributes($group) protected function getDefaultAttributes($group)
{ {
return [ return [
'id' => (int) $group->id,
'nameSingular' => $group->name_singular, 'nameSingular' => $group->name_singular,
'namePlural' => $group->name_plural, 'namePlural' => $group->name_plural,
'color' => $group->color, 'color' => $group->color,
'icon' => $group->icon, 'icon' => $group->icon,
]; ];
} }
/**
* @return callable
*/
protected function permissions()
{
return $this->hasMany('Flarum\Api\Serializers\PermissionSerializer');
}
} }

View File

@ -36,4 +36,14 @@ class Group extends Model
{ {
return $this->belongsToMany('Flarum\Core\Users\User', 'users_groups'); return $this->belongsToMany('Flarum\Core\Users\User', 'users_groups');
} }
/**
* Define the relationship with the group's permissions.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function permissions()
{
return $this->hasMany('Flarum\Core\Groups\Permission');
}
} }

View File

@ -0,0 +1,55 @@
<?php namespace Flarum\Core\Groups;
use Flarum\Core\Model;
use Illuminate\Database\Eloquent\Builder;
/**
* @todo document database columns with @property
*/
class Permission extends Model
{
/**
* {@inheritdoc}
*/
protected $table = 'permissions';
/**
* Define the relationship with the group that this permission is for.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function group()
{
return $this->belongsTo('Flarum\Core\Groups\Group', 'group_id');
}
/**
* Set the keys for a save update query.
*
* @param Builder $query
* @return Builder
*/
protected function setKeysForSaveQuery(Builder $query)
{
$query->where('group_id', $this->group_id)
->where('permission', $this->permission);
return $query;
}
/**
* Get a map of permissions to the group IDs that have them.
*
* @return array[]
*/
public static function map()
{
$permissions = [];
foreach (static::get() as $permission) {
$permissions[$permission->permission][] = $permission->group_id;
}
return $permissions;
}
}