grafana/public/app/features/admin/UserOrgs.tsx
Alexander Zobnin 8505d90768 Admin: New Admin User page (#20498)
* admin: user page to react WIP

* admin user page: basic view

* admin user page: refactor, extract orgs and permissions components

* admin user: change sessions actions styles

* admin user: add disable button

* user admin: add change grafana admin action

* user admin: able to change org role and remove org

* user admin: confirm force logout

* user admin: change org button style

* user admin: add confirm modals for critical actions

* user admin: lock down ldap user info

* user admin: align with latest design changes

* user admin: add LDAP sync

* admin user: confirm button

* user admin: add to org modal

* user admin: fix ConfirmButton story

* admin user: handle grafana admin change

* ConfirmButton: make styled component

* ConfirmButton: completely styled component

* User Admin: permissions section refactor

* admin user: refactor (orgs and sessions)

* ConfirmButton: able to set confirm variant

* admin user: inline org removal

* admin user: show ldap sync info only for ldap users

* admin user: edit profile

* ConfirmButton: some fixes after review

* Chore: fix storybook build

* admin user: rename handlers

* admin user: remove LdapUserPage import from routes

* Chore: fix ConfirmButton tests

* Chore: fix user api endpoint tests

* Chore: update failed test snapshots

* admin user: redux actions WIP

* admin user: use new ConfirmModal component for user profile

* admin user: use new ConfirmModal component for sessions

* admin user: use lockMessage

* ConfirmButton: use primary button as default

* admin user: fix ActionButton color

* UI: use Icon component for Modal

* UI: refactor ConfirmModal after Modal changes

* UI: add link button variant

* UI: able to use custom ConfirmButton

* Chore: fix type errors after ConfirmButton refactor

* Chore: revert Graph component changes (works with TS 3.7)

* Chore: use Forms.Button instead of ActionButton

* admin user: align items

* admin user: align add to org modal

* UI: organization picker component

* admin user: use org picker for AddToOrgModal

* admin user: org actions

* admin user: connect sessions actions

* admin user: updateUserPermissions action

* admin user: enable delete user action

* admin user: sync ldap user

* Chore: refactor, remove unused code

* Chore: refactor, move api calls to actions

* admin user: set user password action

* Chore: refactor, remove unused components

* admin user: set input focus on edit

* admin user: pass user into debug LDAP mapping

* UserAdminPage: Ux changes

* UserAdminPage: align buttons to the left

* UserAdminPage: align delete user button

* UserAdminPage: swap add to org modal buttons

* UserAdminPage: set password field to empty when editing

* UserAdminPage: fix tests

* Updated button border

* Chore: fix ConfirmButton after changes introduced in #21092

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2020-01-13 17:10:19 +01:00

272 lines
7.4 KiB
TypeScript

import React, { PureComponent } from 'react';
import { css, cx } from 'emotion';
import { Modal, Themeable, stylesFactory, withTheme, ConfirmButton, Forms } from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data';
import { UserOrg, Organization } from 'app/types';
import { OrgPicker, OrgSelectItem } from 'app/core/components/Select/OrgPicker';
interface Props {
orgs: UserOrg[];
onOrgRemove: (orgId: number) => void;
onOrgRoleChange: (orgId: number, newRole: string) => void;
onOrgAdd: (orgId: number, role: string) => 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;
`;
return (
<>
<h3 className="page-heading">Organisations</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}>
<Forms.Button variant="secondary" onClick={this.showOrgAddModal(true)}>
Add user to organization
</Forms.Button>
</div>
<AddToOrgModal isOpen={showAddOrgModal} onOrgAdd={onOrgAdd} onDismiss={this.showOrgAddModal(false)} />
</div>
</>
);
}
}
const ORG_ROLES = ['Viewer', 'Editor', 'Admin'];
const getOrgRowStyles = stylesFactory((theme: GrafanaTheme) => {
return {
removeButton: css`
margin-right: 0.6rem;
text-decoration: underline;
color: ${theme.colors.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: string;
isChangingRole: boolean;
isRemovingFromOrg: boolean;
}
class UnThemedOrgRow extends PureComponent<OrgRowProps, OrgRowState> {
state = {
currentRole: this.props.org.role,
isChangingRole: false,
isRemovingFromOrg: false,
};
onOrgRemove = () => {
const { org } = this.props;
this.props.onOrgRemove(org.orgId);
};
onChangeRoleClick = () => {
const { org } = this.props;
this.setState({ isChangingRole: true, currentRole: org.role });
};
onOrgRemoveClick = () => {
this.setState({ isRemovingFromOrg: true });
};
onOrgRoleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const newRole = event.target.value;
this.setState({ currentRole: newRole });
};
onOrgRoleSave = () => {
this.props.onOrgRoleChange(this.props.org.orgId, this.state.currentRole);
};
onCancelClick = () => {
this.setState({ isChangingRole: false, isRemovingFromOrg: false });
};
render() {
const { org, theme } = this.props;
const { currentRole, isChangingRole, isRemovingFromOrg } = this.state;
const styles = getOrgRowStyles(theme);
const labelClass = cx('width-16', styles.label);
return (
<tr>
<td className={labelClass}>{org.name}</td>
{isChangingRole ? (
<td>
<div className="gf-form-select-wrapper width-8">
<select value={currentRole} className="gf-form-input" onChange={this.onOrgRoleChange}>
{ORG_ROLES.map((option, index) => {
return (
<option value={option} key={`${option}-${index}`}>
{option}
</option>
);
})}
</select>
</div>
</td>
) : (
<td className="width-25">{org.role}</td>
)}
{!isRemovingFromOrg && (
<td colSpan={isChangingRole ? 2 : 1}>
<div className="pull-right">
<ConfirmButton
confirmText="Save"
onClick={this.onChangeRoleClick}
onCancel={this.onCancelClick}
onConfirm={this.onOrgRoleSave}
>
Change role
</ConfirmButton>
</div>
</td>
)}
{!isChangingRole && (
<td colSpan={isRemovingFromOrg ? 2 : 1}>
<div className="pull-right">
<ConfirmButton
confirmText="Confirm removal"
confirmVariant="danger"
onClick={this.onOrgRemoveClick}
onCancel={this.onCancelClick}
onConfirm={this.onOrgRemove}
>
Remove from organisation
</ConfirmButton>
</div>
</td>
)}
</tr>
);
}
}
const OrgRow = withTheme(UnThemedOrgRow);
const getAddToOrgModalStyles = stylesFactory(() => ({
modal: css`
width: 500px;
`,
buttonRow: css`
text-align: center;
`,
}));
interface AddToOrgModalProps {
isOpen: boolean;
onOrgAdd(orgId: number, role: string): void;
onDismiss?(): void;
}
interface AddToOrgModalState {
selectedOrg: Organization;
role: string;
}
export class AddToOrgModal extends PureComponent<AddToOrgModalProps, AddToOrgModalState> {
state: AddToOrgModalState = {
selectedOrg: null,
role: 'Admin',
};
onOrgSelect = (org: OrgSelectItem) => {
this.setState({ selectedOrg: { ...org } });
};
onOrgRoleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
this.setState({
role: event.target.value,
});
};
onAddUserToOrg = () => {
const { selectedOrg, role } = this.state;
this.props.onOrgAdd(selectedOrg.id, role);
};
onCancel = () => {
this.props.onDismiss();
};
render() {
const { isOpen } = this.props;
const { role } = this.state;
const styles = getAddToOrgModalStyles();
const buttonRowClass = cx('gf-form-button-row', styles.buttonRow);
return (
<Modal className={styles.modal} title="Add to an organization" isOpen={isOpen} onDismiss={this.onCancel}>
<div className="gf-form-group">
<h6 className="">Organisation</h6>
<OrgPicker className="width-25" onSelected={this.onOrgSelect} />
</div>
<div className="gf-form-group">
<h6 className="">Role</h6>
<div className="gf-form-select-wrapper width-16">
<select value={role} className="gf-form-input" onChange={this.onOrgRoleChange}>
{ORG_ROLES.map((option, index) => {
return (
<option value={option} key={`${option}-${index}`}>
{option}
</option>
);
})}
</select>
</div>
</div>
<div className={buttonRowClass}>
<Forms.Button variant="primary" onClick={this.onAddUserToOrg}>
Add to organization
</Forms.Button>
<Forms.Button variant="secondary" onClick={this.onCancel}>
Cancel
</Forms.Button>
</div>
</Modal>
);
}
}