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 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// load groups
|
||||
// load teams
|
||||
teams, err := g.getTeams()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// evalute group rules
|
||||
// evalute team rules
|
||||
for _, p := range acl {
|
||||
for _, ug := range teams {
|
||||
if ug.Id == p.TeamId && p.Permission >= permission {
|
||||
@@ -140,7 +140,7 @@ func (g *DashboardGuardian) CheckPermissionBeforeUpdate(permission m.PermissionT
|
||||
return g.checkAcl(permission, acl)
|
||||
}
|
||||
|
||||
// Returns dashboard acl
|
||||
// GetAcl returns dashboard acl
|
||||
func (g *DashboardGuardian) GetAcl() ([]*m.DashboardAclInfoDTO, error) {
|
||||
if g.acl != nil {
|
||||
return g.acl, nil
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { Component } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { store } from 'app/stores/store';
|
||||
import Permissions from 'app/core/components/Permissions/Permissions';
|
||||
|
||||
@@ -9,7 +8,6 @@ export interface IProps {
|
||||
backendSrv: any;
|
||||
}
|
||||
|
||||
@observer
|
||||
class DashboardPermissions extends Component<IProps, any> {
|
||||
permissions: any;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker';
|
||||
import { permissionOptions } from 'app/stores/PermissionsStore/PermissionsStore';
|
||||
|
||||
export interface IProps {
|
||||
@@ -18,16 +19,13 @@ export default class DisabledPermissionListItem extends Component<IProps, any> {
|
||||
<td />
|
||||
<td className="query-keyword">Can</td>
|
||||
<td>
|
||||
<div className="gf-form-select-wrapper">
|
||||
<select value={item.permission} className="gf-form-input gf-size-auto" disabled={true}>
|
||||
{permissionOptions.map((option, idx) => {
|
||||
return (
|
||||
<option key={idx} value={option.value}>
|
||||
{option.text}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<div className="gf-form">
|
||||
<DescriptionPicker
|
||||
optionsWithDesc={permissionOptions}
|
||||
handlePicked={() => {}}
|
||||
value={item.permission}
|
||||
disabled={true}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -142,7 +142,7 @@ class Permissions extends Component<IProps, any> {
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="empty-list-cta m-t-3">
|
||||
{/* <div className="empty-list-cta m-t-3">
|
||||
<div className="grafana-info-box">
|
||||
<h5>What are Permissions?</h5>
|
||||
<p>
|
||||
@@ -157,7 +157,7 @@ class Permissions extends Component<IProps, any> {
|
||||
</a>{' '}
|
||||
for more information.
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker';
|
||||
import { permissionOptions } from 'app/stores/PermissionsStore/PermissionsStore';
|
||||
|
||||
const setClassNameHelper = inherited => {
|
||||
@@ -12,12 +13,8 @@ export default observer(({ item, removeItem, permissionChanged, itemIndex, folde
|
||||
removeItem(itemIndex);
|
||||
};
|
||||
|
||||
const handleChangePermission = evt => {
|
||||
evt.preventDefault();
|
||||
const value = evt.target.value;
|
||||
const valueAsInt = parseInt(value, 10);
|
||||
const newPermission = permissionOptions.find(opt => opt.value === valueAsInt);
|
||||
permissionChanged(itemIndex, newPermission.value, newPermission.text);
|
||||
const handleChangePermission = permissionOption => {
|
||||
permissionChanged(itemIndex, permissionOption.value, permissionOption.label);
|
||||
};
|
||||
|
||||
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 className="query-keyword">Can</td>
|
||||
<td>
|
||||
<div className="gf-form-select-wrapper">
|
||||
<select
|
||||
<div className="gf-form">
|
||||
<DescriptionPicker
|
||||
optionsWithDesc={permissionOptions}
|
||||
handlePicked={handleChangePermission}
|
||||
value={item.permission}
|
||||
className="gf-form-input gf-size-auto"
|
||||
onChange={handleChangePermission}
|
||||
disabled={item.inherited}
|
||||
>
|
||||
{permissionOptions.map((option, idx) => {
|
||||
return (
|
||||
<option key={idx} value={option.value}>
|
||||
{option.text}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
/>
|
||||
</div>
|
||||
</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.';
|
||||
|
||||
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 = [
|
||||
{ value: 'Group', text: 'Team' },
|
||||
|
||||
@@ -9,6 +9,9 @@ $select-noresults-color: $text-color;
|
||||
$select-input-bg: $input-bg;
|
||||
$select-input-border-color: $input-border-color;
|
||||
$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';
|
||||
|
||||
@@ -36,6 +39,10 @@ $select-menu-box-shadow: $menu-dropdown-shadow;
|
||||
color: $input-color-placeholder;
|
||||
}
|
||||
|
||||
.Select-menu-outer {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
> .Select-control {
|
||||
@include select-control();
|
||||
border-color: $dark-3;
|
||||
@@ -51,15 +58,11 @@ $select-menu-box-shadow: $menu-dropdown-shadow;
|
||||
|
||||
.Select-value {
|
||||
display: inline-block;
|
||||
padding: 2px 4px;
|
||||
font-size: $font-size-base * 0.846;
|
||||
font-weight: bold;
|
||||
line-height: 14px; // ensure proper line-height if floated
|
||||
color: $white;
|
||||
padding: $input-padding-y $input-padding-x;
|
||||
font-size: $font-size-md;
|
||||
line-height: $input-line-height;
|
||||
vertical-align: baseline;
|
||||
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