mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 00:55:47 -06:00
* 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>
272 lines
7.4 KiB
TypeScript
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>
|
|
);
|
|
}
|
|
}
|