grafana/public/app/features/admin/UserOrgs.tsx
Alexander Zobnin a7e721e987
Access control: Make Admin/Users UI working with the permissions (#33176)
* API: authorize admin/users views

* Render admin/users components based on user's permissions

* Add LDAP permissions (required by admin/user page)

* Extend default admin role by LDAP permissions

* Show/hide LDAP debug views

* Render LDAP debug page if user has access

* Authorize LDAP debug view

* fix permissions definitions

* Add LDAP page permissions

* remove ambiguous permissions check

* Hide logout buttons in sessions table

* Add org/users permissions

* Use org permissions for managing user roles in orgs

* Apply permissions to org/users

* Apply suggestions from review

* Fix tests

* remove scopes from the frontend

* Tweaks according to review

* Handle /invites endpoints
2021-04-22 13:19:41 +03:00

268 lines
6.9 KiB
TypeScript

import React, { PureComponent } from 'react';
import { css, cx } from '@emotion/css';
import {
Button,
ConfirmButton,
Container,
Field,
HorizontalGroup,
Modal,
stylesFactory,
Themeable,
withTheme,
} from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data';
import { AccessControlAction, Organization, OrgRole, UserOrg } from 'app/types';
import { OrgPicker, OrgSelectItem } from 'app/core/components/Select/OrgPicker';
import { OrgRolePicker } from './OrgRolePicker';
import { contextSrv } from 'app/core/core';
interface Props {
orgs: UserOrg[];
onOrgRemove: (orgId: number) => void;
onOrgRoleChange: (orgId: number, newRole: OrgRole) => void;
onOrgAdd: (orgId: number, role: OrgRole) => void;
}
interface State {
showAddOrgModal: boolean;
}
export class UserOrgs extends PureComponent<Props, State> {
state = {
showAddOrgModal: false,
};
showOrgAddModal = (show: boolean) => () => {
this.setState({ showAddOrgModal: show });
};
render() {
const { orgs, onOrgRoleChange, onOrgRemove, onOrgAdd } = this.props;
const { showAddOrgModal } = this.state;
const addToOrgContainerClass = css`
margin-top: 0.8rem;
`;
const canAddToOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersAdd);
return (
<>
<h3 className="page-heading">Organizations</h3>
<div className="gf-form-group">
<div className="gf-form">
<table className="filter-table form-inline">
<tbody>
{orgs.map((org, index) => (
<OrgRow
key={`${org.orgId}-${index}`}
org={org}
onOrgRoleChange={onOrgRoleChange}
onOrgRemove={onOrgRemove}
/>
))}
</tbody>
</table>
</div>
<div className={addToOrgContainerClass}>
{canAddToOrg && (
<Button variant="secondary" onClick={this.showOrgAddModal(true)}>
Add user to organization
</Button>
)}
</div>
<AddToOrgModal isOpen={showAddOrgModal} onOrgAdd={onOrgAdd} onDismiss={this.showOrgAddModal(false)} />
</div>
</>
);
}
}
const getOrgRowStyles = stylesFactory((theme: GrafanaTheme) => {
return {
removeButton: css`
margin-right: 0.6rem;
text-decoration: underline;
color: ${theme.palette.blue95};
`,
label: css`
font-weight: 500;
`,
};
});
interface OrgRowProps extends Themeable {
org: UserOrg;
onOrgRemove: (orgId: number) => void;
onOrgRoleChange: (orgId: number, newRole: string) => void;
}
interface OrgRowState {
currentRole: OrgRole;
isChangingRole: boolean;
}
class UnThemedOrgRow extends PureComponent<OrgRowProps, OrgRowState> {
state = {
currentRole: this.props.org.role,
isChangingRole: false,
};
onOrgRemove = () => {
const { org } = this.props;
this.props.onOrgRemove(org.orgId);
};
onChangeRoleClick = () => {
const { org } = this.props;
this.setState({ isChangingRole: true, currentRole: org.role });
};
onOrgRoleChange = (newRole: OrgRole) => {
this.setState({ currentRole: newRole });
};
onOrgRoleSave = () => {
this.props.onOrgRoleChange(this.props.org.orgId, this.state.currentRole);
};
onCancelClick = () => {
this.setState({ isChangingRole: false });
};
render() {
const { org, theme } = this.props;
const { currentRole, isChangingRole } = this.state;
const styles = getOrgRowStyles(theme);
const labelClass = cx('width-16', styles.label);
const canChangeRole = contextSrv.hasPermission(AccessControlAction.OrgUsersRoleUpdate);
const canRemoveFromOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersRemove);
return (
<tr>
<td className={labelClass}>{org.name}</td>
{isChangingRole ? (
<td>
<OrgRolePicker value={currentRole} onChange={this.onOrgRoleChange} />
</td>
) : (
<td className="width-25">{org.role}</td>
)}
<td colSpan={1}>
<div className="pull-right">
{canChangeRole && (
<ConfirmButton
confirmText="Save"
onClick={this.onChangeRoleClick}
onCancel={this.onCancelClick}
onConfirm={this.onOrgRoleSave}
>
Change role
</ConfirmButton>
)}
</div>
</td>
<td colSpan={1}>
<div className="pull-right">
{canRemoveFromOrg && (
<ConfirmButton
confirmText="Confirm removal"
confirmVariant="destructive"
onCancel={this.onCancelClick}
onConfirm={this.onOrgRemove}
>
Remove from organization
</ConfirmButton>
)}
</div>
</td>
</tr>
);
}
}
const OrgRow = withTheme(UnThemedOrgRow);
const getAddToOrgModalStyles = stylesFactory(() => ({
modal: css`
width: 500px;
`,
buttonRow: css`
text-align: center;
`,
modalContent: css`
overflow: visible;
`,
}));
interface AddToOrgModalProps {
isOpen: boolean;
onOrgAdd(orgId: number, role: string): void;
onDismiss?(): void;
}
interface AddToOrgModalState {
selectedOrg: Organization | null;
role: OrgRole;
}
export class AddToOrgModal extends PureComponent<AddToOrgModalProps, AddToOrgModalState> {
state: AddToOrgModalState = {
selectedOrg: null,
role: OrgRole.Admin,
};
onOrgSelect = (org: OrgSelectItem) => {
this.setState({ selectedOrg: { ...org } });
};
onOrgRoleChange = (newRole: OrgRole) => {
this.setState({
role: newRole,
});
};
onAddUserToOrg = () => {
const { selectedOrg, role } = this.state;
this.props.onOrgAdd(selectedOrg!.id, role);
};
onCancel = () => {
if (this.props.onDismiss) {
this.props.onDismiss();
}
};
render() {
const { isOpen } = this.props;
const { role } = this.state;
const styles = getAddToOrgModalStyles();
return (
<Modal
className={styles.modal}
contentClassName={styles.modalContent}
title="Add to an organization"
isOpen={isOpen}
onDismiss={this.onCancel}
>
<Field label="Organization">
<OrgPicker onSelected={this.onOrgSelect} />
</Field>
<Field label="Role">
<OrgRolePicker value={role} onChange={this.onOrgRoleChange} />
</Field>
<Container padding="md">
<HorizontalGroup spacing="md" justify="center">
<Button variant="primary" onClick={this.onAddUserToOrg}>
Add to organization
</Button>
<Button variant="secondary" onClick={this.onCancel}>
Cancel
</Button>
</HorizontalGroup>
</Container>
</Modal>
);
}
}