TeamGroupSync.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import React, { PureComponent } from 'react';
  2. import { connect, ConnectedProps } from 'react-redux';
  3. import { Input, Tooltip, Icon, Button, useTheme2, InlineField, InlineFieldRow } from '@grafana/ui';
  4. import { SlideDown } from 'app/core/components/Animations/SlideDown';
  5. import { CloseButton } from 'app/core/components/CloseButton/CloseButton';
  6. import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
  7. import { UpgradeBox, UpgradeContent, UpgradeContentProps } from 'app/core/components/Upgrade/UpgradeBox';
  8. import { highlightTrial } from 'app/features/admin/utils';
  9. import { StoreState, TeamGroup } from '../../types';
  10. import { addTeamGroup, loadTeamGroups, removeTeamGroup } from './state/actions';
  11. import { getTeamGroups } from './state/selectors';
  12. function mapStateToProps(state: StoreState) {
  13. return {
  14. groups: getTeamGroups(state.team),
  15. };
  16. }
  17. const mapDispatchToProps = {
  18. loadTeamGroups,
  19. addTeamGroup,
  20. removeTeamGroup,
  21. };
  22. interface OwnProps {
  23. isReadOnly: boolean;
  24. }
  25. interface State {
  26. isAdding: boolean;
  27. newGroupId: string;
  28. }
  29. const connector = connect(mapStateToProps, mapDispatchToProps);
  30. export type Props = OwnProps & ConnectedProps<typeof connector>;
  31. const headerTooltip = `Sync LDAP or OAuth groups with your Grafana teams.`;
  32. export class TeamGroupSync extends PureComponent<Props, State> {
  33. constructor(props: Props) {
  34. super(props);
  35. this.state = { isAdding: false, newGroupId: '' };
  36. }
  37. componentDidMount() {
  38. this.fetchTeamGroups();
  39. }
  40. async fetchTeamGroups() {
  41. await this.props.loadTeamGroups();
  42. }
  43. onToggleAdding = () => {
  44. this.setState({ isAdding: !this.state.isAdding });
  45. };
  46. onNewGroupIdChanged = (event: any) => {
  47. this.setState({ newGroupId: event.target.value });
  48. };
  49. onAddGroup = (event: any) => {
  50. event.preventDefault();
  51. this.props.addTeamGroup(this.state.newGroupId);
  52. this.setState({ isAdding: false, newGroupId: '' });
  53. };
  54. onRemoveGroup = (group: TeamGroup) => {
  55. this.props.removeTeamGroup(group.groupId);
  56. };
  57. isNewGroupValid() {
  58. return this.state.newGroupId.length > 1;
  59. }
  60. renderGroup(group: TeamGroup) {
  61. const { isReadOnly } = this.props;
  62. return (
  63. <tr key={group.groupId}>
  64. <td>{group.groupId}</td>
  65. <td style={{ width: '1%' }}>
  66. <Button
  67. size="sm"
  68. variant="destructive"
  69. onClick={() => this.onRemoveGroup(group)}
  70. disabled={isReadOnly}
  71. aria-label={`Remove group ${group.groupId}`}
  72. >
  73. <Icon name="times" />
  74. </Button>
  75. </td>
  76. </tr>
  77. );
  78. }
  79. render() {
  80. const { isAdding, newGroupId } = this.state;
  81. const { groups, isReadOnly } = this.props;
  82. return (
  83. <div>
  84. {highlightTrial() && (
  85. <UpgradeBox
  86. featureId={'team-sync'}
  87. eventVariant={'trial'}
  88. featureName={'team sync'}
  89. text={'Add a group to enable team sync for free during your trial of Grafana Pro.'}
  90. />
  91. )}
  92. <div className="page-action-bar">
  93. {(!highlightTrial() || groups.length > 0) && (
  94. <>
  95. <h3 className="page-sub-heading">External group sync</h3>
  96. <Tooltip placement="auto" content={headerTooltip}>
  97. <Icon className="icon--has-hover page-sub-heading-icon" name="question-circle" />
  98. </Tooltip>
  99. </>
  100. )}
  101. <div className="page-action-bar__spacer" />
  102. {groups.length > 0 && (
  103. <Button className="pull-right" onClick={this.onToggleAdding} disabled={isReadOnly}>
  104. <Icon name="plus" /> Add group
  105. </Button>
  106. )}
  107. </div>
  108. <SlideDown in={isAdding}>
  109. <div className="cta-form">
  110. <CloseButton onClick={this.onToggleAdding} />
  111. <form onSubmit={this.onAddGroup}>
  112. <InlineFieldRow>
  113. <InlineField label={'Add External Group'}>
  114. <Input
  115. type="text"
  116. id={'add-external-group'}
  117. value={newGroupId}
  118. onChange={this.onNewGroupIdChanged}
  119. placeholder="cn=ops,ou=groups,dc=grafana,dc=org"
  120. disabled={isReadOnly}
  121. />
  122. </InlineField>
  123. <Button type="submit" disabled={isReadOnly || !this.isNewGroupValid()} style={{ marginLeft: 4 }}>
  124. Add group
  125. </Button>
  126. </InlineFieldRow>
  127. </form>
  128. </div>
  129. </SlideDown>
  130. {groups.length === 0 &&
  131. !isAdding &&
  132. (highlightTrial() ? (
  133. <TeamSyncUpgradeContent action={{ onClick: this.onToggleAdding, text: 'Add group' }} />
  134. ) : (
  135. <EmptyListCTA
  136. onClick={this.onToggleAdding}
  137. buttonIcon="users-alt"
  138. title="There are no external groups to sync with"
  139. buttonTitle="Add group"
  140. proTip={headerTooltip}
  141. proTipLinkTitle="Learn more"
  142. proTipLink="https://docs.grafana.org/auth/enhanced_ldap/"
  143. proTipTarget="_blank"
  144. buttonDisabled={isReadOnly}
  145. />
  146. ))}
  147. {groups.length > 0 && (
  148. <div className="admin-list-table">
  149. <table className="filter-table filter-table--hover form-inline">
  150. <thead>
  151. <tr>
  152. <th>External Group ID</th>
  153. <th style={{ width: '1%' }} />
  154. </tr>
  155. </thead>
  156. <tbody>{groups.map((group) => this.renderGroup(group))}</tbody>
  157. </table>
  158. </div>
  159. )}
  160. </div>
  161. );
  162. }
  163. }
  164. export const TeamSyncUpgradeContent = ({ action }: { action?: UpgradeContentProps['action'] }) => {
  165. const theme = useTheme2();
  166. return (
  167. <UpgradeContent
  168. action={action}
  169. listItems={[
  170. 'Stop managing user access in two places - assign users to groups in SAML, LDAP or Oauth, and manage access at a Team level in Grafana',
  171. 'Update users’ permissions immediately when you add or remove them from an LDAP group, with no need for them to sign out and back in',
  172. ]}
  173. image={`team-sync-${theme.isLight ? 'light' : 'dark'}.png`}
  174. featureName={'team sync'}
  175. featureUrl={'https://grafana.com/docs/grafana/latest/enterprise/team-sync'}
  176. description={
  177. 'Team Sync makes it easier for you to manage users’ access in Grafana, by immediately updating each user’s Grafana teams and permissions based on their single sign-on group membership, instead of when users sign in.'
  178. }
  179. />
  180. );
  181. };
  182. export default connect(mapStateToProps, mapDispatchToProps)(TeamGroupSync);