diff --git a/public/app/features/admin/ldap/LdapPage.tsx b/public/app/features/admin/ldap/LdapPage.tsx index 4c6bc32f0e7..86e6c1f48dd 100644 --- a/public/app/features/admin/ldap/LdapPage.tsx +++ b/public/app/features/admin/ldap/LdapPage.tsx @@ -1,6 +1,5 @@ import { useCallback, useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; -import { connect, ConnectedProps } from 'react-redux'; import { NavModelItem } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; @@ -11,8 +10,7 @@ import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { AccessControlAction } from 'app/types/accessControl'; import { AppNotificationSeverity } from 'app/types/appNotifications'; -import { LdapConnectionInfo, LdapUser, SyncInfo, LdapError } from 'app/types/ldap'; -import { StoreState } from 'app/types/store'; +import { useDispatch, useSelector } from 'app/types/store'; import { loadLdapState, @@ -26,13 +24,7 @@ import { LdapConnectionStatus } from './LdapConnectionStatus'; import { LdapSyncInfo } from './LdapSyncInfo'; import { LdapUserInfo } from './LdapUserInfo'; -interface OwnProps extends GrafanaRouteComponentProps<{}, { username?: string }> { - ldapConnectionInfo: LdapConnectionInfo; - ldapUser?: LdapUser; - ldapSyncInfo?: SyncInfo; - ldapError?: LdapError; - userError?: LdapError; -} +interface Props extends GrafanaRouteComponentProps<{}, { username?: string }> {} interface FormModel { username: string; @@ -45,36 +37,31 @@ const pageNav: NavModelItem = { id: 'LDAP', }; -export const LdapPage = ({ - clearUserMappingInfo, - queryParams, - loadLdapState, - loadLdapSyncStatus, - loadUserMapping, - clearUserError, - ldapUser, - userError, - ldapError, - ldapSyncInfo, - ldapConnectionInfo, -}: Props) => { +export const LdapPage = ({ queryParams }: Props) => { + const dispatch = useDispatch(); + + const ldapConnectionInfo = useSelector((state) => state.ldap.connectionInfo); + const ldapUser = useSelector((state) => state.ldap.user); + const ldapSyncInfo = useSelector((state) => state.ldap.syncInfo); + const userError = useSelector((state) => state.ldap.userError); + const ldapError = useSelector((state) => state.ldap.ldapError); const [isLoading, setIsLoading] = useState(true); const { register, handleSubmit } = useForm(); const fetchUserMapping = useCallback( async (username: string) => { - return loadUserMapping(username); + return dispatch(loadUserMapping(username)); }, - [loadUserMapping] + [dispatch] ); useEffect(() => { const fetchLDAPStatus = async () => { - return Promise.all([loadLdapState(), loadLdapSyncStatus()]); + return Promise.all([dispatch(loadLdapState()), dispatch(loadLdapSyncStatus())]); }; async function init() { - await clearUserMappingInfo(); + await dispatch(clearUserMappingInfo()); await fetchLDAPStatus(); if (queryParams.username) { @@ -85,7 +72,7 @@ export const LdapPage = ({ } init(); - }, [clearUserMappingInfo, fetchUserMapping, loadLdapState, loadLdapSyncStatus, queryParams]); + }, [dispatch, fetchUserMapping, queryParams]); const search = ({ username }: FormModel) => { if (username) { @@ -94,7 +81,7 @@ export const LdapPage = ({ }; const onClearUserError = () => { - clearUserError(); + dispatch(clearUserError()); }; const canReadLDAPUser = contextSrv.hasPermission(AccessControlAction.LDAPUsersRead); @@ -147,23 +134,4 @@ export const LdapPage = ({ ); }; -const mapStateToProps = (state: StoreState) => ({ - ldapConnectionInfo: state.ldap.connectionInfo, - ldapUser: state.ldap.user, - ldapSyncInfo: state.ldap.syncInfo, - userError: state.ldap.userError, - ldapError: state.ldap.ldapError, -}); - -const mapDispatchToProps = { - loadLdapState, - loadLdapSyncStatus, - loadUserMapping, - clearUserError, - clearUserMappingInfo, -}; - -const connector = connect(mapStateToProps, mapDispatchToProps); -type Props = OwnProps & ConnectedProps; - -export default connector(LdapPage); +export default LdapPage; diff --git a/public/app/features/admin/ldap/LdapSettingsPage.tsx b/public/app/features/admin/ldap/LdapSettingsPage.tsx index b100c9386fb..3d597481e17 100644 --- a/public/app/features/admin/ldap/LdapSettingsPage.tsx +++ b/public/app/features/admin/ldap/LdapSettingsPage.tsx @@ -31,6 +31,7 @@ import { LdapPayload, MapKeyCertConfigured } from 'app/types/ldap'; import { StoreState } from 'app/types/store'; import { LdapDrawerComponent } from './LdapDrawer'; +import { LdapTestDrawer } from './LdapTestDrawer'; const appEvents = getAppEvents(); @@ -99,6 +100,8 @@ const emptySettings: LdapPayload = { export const LdapSettingsPage = () => { const [isLoading, setIsLoading] = useState(true); const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [isTestDrawerOpen, setIsTestDrawerOpen] = useState(false); + const [usernameParam, setUsernameParam] = useState(null); const [isBindPasswordConfigured, setBindPasswordConfigured] = useState(false); const [mapKeyCertConfigured, setMapKeyCertConfigured] = useState({ @@ -122,6 +125,10 @@ export const LdapSettingsPage = () => { useEffect(() => { async function init() { + const urlParams = new URLSearchParams(window.location.search); + const username = urlParams.get('username'); + setUsernameParam(username); + const payload = await getSettings(); let serverConfig = emptySettings.settings.config.servers[0]; if (payload.settings.config.servers?.length > 0) { @@ -135,6 +142,10 @@ export const LdapSettingsPage = () => { reset(payload); setIsLoading(false); + + if (username) { + setIsTestDrawerOpen(true); + } } init(); }, [reset]); // eslint-disable-line react-hooks/exhaustive-deps @@ -416,6 +427,9 @@ export const LdapSettingsPage = () => { + Discard @@ -455,6 +469,9 @@ export const LdapSettingsPage = () => { /> )} + {isTestDrawerOpen && ( + setIsTestDrawerOpen(false)} username={usernameParam || undefined} /> + )} diff --git a/public/app/features/admin/ldap/LdapTestDrawer.tsx b/public/app/features/admin/ldap/LdapTestDrawer.tsx new file mode 100644 index 00000000000..8a791dda530 --- /dev/null +++ b/public/app/features/admin/ldap/LdapTestDrawer.tsx @@ -0,0 +1,138 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; + +import { Trans, t } from '@grafana/i18n'; +import { featureEnabled } from '@grafana/runtime'; +import { Alert, Button, Drawer, Field, Input, LoadingPlaceholder, Stack, Text } from '@grafana/ui'; +import { contextSrv } from 'app/core/core'; +import { AccessControlAction } from 'app/types/accessControl'; +import { AppNotificationSeverity } from 'app/types/appNotifications'; +import { useDispatch, useSelector } from 'app/types/store'; + +import { + loadLdapState, + loadLdapSyncStatus, + loadUserMapping, + clearUserError, + clearUserMappingInfo, +} from '../state/actions'; + +import { LdapConnectionStatus } from './LdapConnectionStatus'; +import { LdapSyncInfo } from './LdapSyncInfo'; +import { LdapUserInfo } from './LdapUserInfo'; + +interface Props { + onClose: () => void; + username?: string; +} + +interface FormModel { + username: string; +} + +export const LdapTestDrawer = ({ onClose, username }: Props) => { + const dispatch = useDispatch(); + + const ldapConnectionInfo = useSelector((state) => state.ldap.connectionInfo); + const ldapUser = useSelector((state) => state.ldap.user); + const ldapSyncInfo = useSelector((state) => state.ldap.syncInfo); + const userError = useSelector((state) => state.ldap.userError); + const ldapError = useSelector((state) => state.ldap.ldapError); + const [isLoading, setIsLoading] = useState(true); + const { register, handleSubmit } = useForm(); + + const fetchUserMapping = useCallback( + async (username: string) => { + return dispatch(loadUserMapping(username)); + }, + [dispatch] + ); + + useEffect(() => { + const fetchLDAPStatus = async () => { + return Promise.all([dispatch(loadLdapState()), dispatch(loadLdapSyncStatus())]); + }; + + async function init() { + dispatch(clearUserMappingInfo()); + await fetchLDAPStatus(); + + if (username) { + await fetchUserMapping(username); + } + + setIsLoading(false); + } + + init(); + }, [dispatch, fetchUserMapping, username]); + + const search = (data: FormModel, event?: React.BaseSyntheticEvent) => { + event?.preventDefault(); + event?.stopPropagation(); + if (data.username) { + fetchUserMapping(data.username); + } + }; + + const onClearUserError = () => { + dispatch(clearUserError()); + }; + + const canReadLDAPUser = contextSrv.hasPermission(AccessControlAction.LDAPUsersRead); + return ( + + {isLoading ? ( + + ) : ( + + {ldapError && ldapError.title && ( + + {ldapError.body} + + )} + + + + {featureEnabled('ldapsync') && ldapSyncInfo && } + + {canReadLDAPUser && ( +
+ + + Test user mapping + +
+ + + + + + +
+ {userError && userError.title && ( + + {userError.body} + + )} + {ldapUser && } +
+
+ )} +
+ )} +
+ ); +}; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 9e4a0376ac0..e92476e2466 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -156,8 +156,11 @@ "title": "Get Grafana Enterprise" }, "ldap": { + "debug-subtitle": "Verify your LDAP and user mapping configuration.", + "debug-title": "LDAP Diagnostics", "test-mapping-heading": "Test user mapping", - "test-mapping-run-button": "Run" + "test-mapping-run-button": "Run", + "text-loading-ldap-status": "Loading LDAP status..." }, "ldap-connection-status": { "columns": { @@ -9239,7 +9242,8 @@ "disable-button": "Disable", "discard-button": "Discard", "save-and-enable-button": "Save and enable", - "save-button": "Save" + "save-button": "Save", + "test-button": "Test" }, "documentation": "documentation", "host": {