mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
dashfolders: select with description for permissions
The dropdown for selecting permission is a new component built on react-select that includes a description for the permission for every option in the select.
This commit is contained in:
@@ -83,18 +83,18 @@ func (g *DashboardGuardian) checkAcl(permission m.PermissionType, acl []*m.Dashb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// do we have group rules?
|
// do we have team rules?
|
||||||
if len(teamAclItems) == 0 {
|
if len(teamAclItems) == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// load groups
|
// load teams
|
||||||
teams, err := g.getTeams()
|
teams, err := g.getTeams()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// evalute group rules
|
// evalute team rules
|
||||||
for _, p := range acl {
|
for _, p := range acl {
|
||||||
for _, ug := range teams {
|
for _, ug := range teams {
|
||||||
if ug.Id == p.TeamId && p.Permission >= permission {
|
if ug.Id == p.TeamId && p.Permission >= permission {
|
||||||
@@ -140,7 +140,7 @@ func (g *DashboardGuardian) CheckPermissionBeforeUpdate(permission m.PermissionT
|
|||||||
return g.checkAcl(permission, acl)
|
return g.checkAcl(permission, acl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns dashboard acl
|
// GetAcl returns dashboard acl
|
||||||
func (g *DashboardGuardian) GetAcl() ([]*m.DashboardAclInfoDTO, error) {
|
func (g *DashboardGuardian) GetAcl() ([]*m.DashboardAclInfoDTO, error) {
|
||||||
if g.acl != nil {
|
if g.acl != nil {
|
||||||
return g.acl, nil
|
return g.acl, nil
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
|
||||||
import { store } from 'app/stores/store';
|
import { store } from 'app/stores/store';
|
||||||
import Permissions from 'app/core/components/Permissions/Permissions';
|
import Permissions from 'app/core/components/Permissions/Permissions';
|
||||||
|
|
||||||
@@ -9,7 +8,6 @@ export interface IProps {
|
|||||||
backendSrv: any;
|
backendSrv: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
|
||||||
class DashboardPermissions extends Component<IProps, any> {
|
class DashboardPermissions extends Component<IProps, any> {
|
||||||
permissions: any;
|
permissions: any;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker';
|
||||||
import { permissionOptions } from 'app/stores/PermissionsStore/PermissionsStore';
|
import { permissionOptions } from 'app/stores/PermissionsStore/PermissionsStore';
|
||||||
|
|
||||||
export interface IProps {
|
export interface IProps {
|
||||||
@@ -18,16 +19,13 @@ export default class DisabledPermissionListItem extends Component<IProps, any> {
|
|||||||
<td />
|
<td />
|
||||||
<td className="query-keyword">Can</td>
|
<td className="query-keyword">Can</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="gf-form-select-wrapper">
|
<div className="gf-form">
|
||||||
<select value={item.permission} className="gf-form-input gf-size-auto" disabled={true}>
|
<DescriptionPicker
|
||||||
{permissionOptions.map((option, idx) => {
|
optionsWithDesc={permissionOptions}
|
||||||
return (
|
handlePicked={() => {}}
|
||||||
<option key={idx} value={option.value}>
|
value={item.permission}
|
||||||
{option.text}
|
disabled={true}
|
||||||
</option>
|
/>
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ class Permissions extends Component<IProps, any> {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="empty-list-cta m-t-3">
|
{/* <div className="empty-list-cta m-t-3">
|
||||||
<div className="grafana-info-box">
|
<div className="grafana-info-box">
|
||||||
<h5>What are Permissions?</h5>
|
<h5>What are Permissions?</h5>
|
||||||
<p>
|
<p>
|
||||||
@@ -157,7 +157,7 @@ class Permissions extends Component<IProps, any> {
|
|||||||
</a>{' '}
|
</a>{' '}
|
||||||
for more information.
|
for more information.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker';
|
||||||
import { permissionOptions } from 'app/stores/PermissionsStore/PermissionsStore';
|
import { permissionOptions } from 'app/stores/PermissionsStore/PermissionsStore';
|
||||||
|
|
||||||
const setClassNameHelper = inherited => {
|
const setClassNameHelper = inherited => {
|
||||||
@@ -12,12 +13,8 @@ export default observer(({ item, removeItem, permissionChanged, itemIndex, folde
|
|||||||
removeItem(itemIndex);
|
removeItem(itemIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangePermission = evt => {
|
const handleChangePermission = permissionOption => {
|
||||||
evt.preventDefault();
|
permissionChanged(itemIndex, permissionOption.value, permissionOption.label);
|
||||||
const value = evt.target.value;
|
|
||||||
const valueAsInt = parseInt(value, 10);
|
|
||||||
const newPermission = permissionOptions.find(opt => opt.value === valueAsInt);
|
|
||||||
permissionChanged(itemIndex, newPermission.value, newPermission.text);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -29,21 +26,13 @@ export default observer(({ item, removeItem, permissionChanged, itemIndex, folde
|
|||||||
<td>{item.inherited ? <em className="muted no-wrap">Inherited from folder {folderTitle} </em> : null}</td>
|
<td>{item.inherited ? <em className="muted no-wrap">Inherited from folder {folderTitle} </em> : null}</td>
|
||||||
<td className="query-keyword">Can</td>
|
<td className="query-keyword">Can</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="gf-form-select-wrapper">
|
<div className="gf-form">
|
||||||
<select
|
<DescriptionPicker
|
||||||
|
optionsWithDesc={permissionOptions}
|
||||||
|
handlePicked={handleChangePermission}
|
||||||
value={item.permission}
|
value={item.permission}
|
||||||
className="gf-form-input gf-size-auto"
|
|
||||||
onChange={handleChangePermission}
|
|
||||||
disabled={item.inherited}
|
disabled={item.inherited}
|
||||||
>
|
/>
|
||||||
{permissionOptions.map((option, idx) => {
|
|
||||||
return (
|
|
||||||
<option key={idx} value={option.value}>
|
|
||||||
{option.text}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
62
public/app/core/components/Picker/DescriptionOption.tsx
Normal file
62
public/app/core/components/Picker/DescriptionOption.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
export interface IProps {
|
||||||
|
onSelect: any;
|
||||||
|
onFocus: any;
|
||||||
|
option: any;
|
||||||
|
isFocused: any;
|
||||||
|
className: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DescriptionOption extends Component<IProps, any> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.handleMouseDown = this.handleMouseDown.bind(this);
|
||||||
|
this.handleMouseEnter = this.handleMouseEnter.bind(this);
|
||||||
|
this.handleMouseMove = this.handleMouseMove.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseDown(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
this.props.onSelect(this.props.option, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseEnter(event) {
|
||||||
|
this.props.onFocus(this.props.option, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseMove(event) {
|
||||||
|
if (this.props.isFocused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.props.onFocus(this.props.option, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { option, children, className } = this.props;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onMouseDown={this.handleMouseDown}
|
||||||
|
onMouseEnter={this.handleMouseEnter}
|
||||||
|
onMouseMove={this.handleMouseMove}
|
||||||
|
title={option.title}
|
||||||
|
className={`user-picker-option__button btn btn-link ${className} width-19`}
|
||||||
|
style={{
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
// height: '55px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="gf-form">{children}</div>
|
||||||
|
<div className="gf-form">
|
||||||
|
<div className="muted width-17">{option.description}</div>
|
||||||
|
{className.indexOf('is-selected') > -1 && (
|
||||||
|
<i style={{ paddingLeft: '2px' }} className="fa fa-check" aria-hidden="true" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DescriptionOption;
|
||||||
47
public/app/core/components/Picker/DescriptionPicker.tsx
Normal file
47
public/app/core/components/Picker/DescriptionPicker.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import Select from 'react-select';
|
||||||
|
import DescriptionOption from './DescriptionOption';
|
||||||
|
|
||||||
|
export interface IProps {
|
||||||
|
optionsWithDesc: OptionWithDescription[];
|
||||||
|
handlePicked: (permission) => void;
|
||||||
|
value: number;
|
||||||
|
disabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OptionWithDescription {
|
||||||
|
value: any;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DescriptionPicker extends Component<IProps, any> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { optionsWithDesc, handlePicked, value, disabled } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="permissions-picker">
|
||||||
|
<Select
|
||||||
|
value={value}
|
||||||
|
valueKey="value"
|
||||||
|
multi={false}
|
||||||
|
clearable={false}
|
||||||
|
labelKey="label"
|
||||||
|
options={optionsWithDesc}
|
||||||
|
onChange={handlePicked}
|
||||||
|
className="width-7 gf-form-input gf-form-input--form-dropdown"
|
||||||
|
optionComponent={DescriptionOption}
|
||||||
|
placeholder="Choose"
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DescriptionPicker;
|
||||||
@@ -3,7 +3,15 @@ import { PermissionsStoreItem } from './PermissionsStoreItem';
|
|||||||
|
|
||||||
const duplicateError = 'This permission exists already.';
|
const duplicateError = 'This permission exists already.';
|
||||||
|
|
||||||
export const permissionOptions = [{ value: 1, text: 'View' }, { value: 2, text: 'Edit' }, { value: 4, text: 'Admin' }];
|
export const permissionOptions = [
|
||||||
|
{ value: 1, label: 'View', description: 'Can view dashboards.' },
|
||||||
|
{ value: 2, label: 'Edit', description: 'Can add, edit and delete dashboards.' },
|
||||||
|
{
|
||||||
|
value: 4,
|
||||||
|
label: 'Admin',
|
||||||
|
description: 'Can add/remove permissions and can add, edit and delete dashboards.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const aclTypes = [
|
export const aclTypes = [
|
||||||
{ value: 'Group', text: 'Team' },
|
{ value: 'Group', text: 'Team' },
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ $select-noresults-color: $text-color;
|
|||||||
$select-input-bg: $input-bg;
|
$select-input-bg: $input-bg;
|
||||||
$select-input-border-color: $input-border-color;
|
$select-input-border-color: $input-border-color;
|
||||||
$select-menu-box-shadow: $menu-dropdown-shadow;
|
$select-menu-box-shadow: $menu-dropdown-shadow;
|
||||||
|
$select-text-color: $text-color;
|
||||||
|
$select-input-bg-disabled: $input-bg-disabled;
|
||||||
|
$select-option-selected-bg: $dropdownLinkBackgroundActive;
|
||||||
|
|
||||||
@import '../../../node_modules/react-select/scss/default.scss';
|
@import '../../../node_modules/react-select/scss/default.scss';
|
||||||
|
|
||||||
@@ -36,6 +39,10 @@ $select-menu-box-shadow: $menu-dropdown-shadow;
|
|||||||
color: $input-color-placeholder;
|
color: $input-color-placeholder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Select-menu-outer {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
> .Select-control {
|
> .Select-control {
|
||||||
@include select-control();
|
@include select-control();
|
||||||
border-color: $dark-3;
|
border-color: $dark-3;
|
||||||
@@ -51,15 +58,11 @@ $select-menu-box-shadow: $menu-dropdown-shadow;
|
|||||||
|
|
||||||
.Select-value {
|
.Select-value {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 2px 4px;
|
padding: $input-padding-y $input-padding-x;
|
||||||
font-size: $font-size-base * 0.846;
|
font-size: $font-size-md;
|
||||||
font-weight: bold;
|
line-height: $input-line-height;
|
||||||
line-height: 14px; // ensure proper line-height if floated
|
|
||||||
color: $white;
|
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
background-color: $gray-1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user