grafana/public/app/features/auth-config/AuthConfigPage.tsx
Alexander Zobnin 7476219b0c
SAML: Configuration UI (#64054)
* Add initial authentication config page skeleton

* Add initial SAML config page WIP

* Add few more pages

* Add connect to IdP page

* Assertion mappings page stub and url params

* Able to save settings

* Some tweaks for authentication page

* Tweak behaviour

* Tweak provider name

* Move SAML config pages to enterprise

* minor refactor

* Able to reset settings

* Configure key and cert from UI

* Refactor WIP

* Tweak styles

* Optional save button

* Some tweaks for the page

* Don't show info popup when save settings

* Improve key/cert validation

* Fetch provider status and display on auth page

* Add settings list to the auth page

* Show call to action card if no auth configured

* clean up

* Show authentication page only if SAML available

* Add access control for SSO config page

* Add feature toggle for auth config UI

* Add code owners for auth config page

* Auth config UI disabled by default

* Fix feature toggle check

* Apply suggestions from review

* Refactor: use forms for steps

* Clean up

* Improve authentication page loading

* Fix CTA link

* Minor tweaks

* Fix page route

* Fix formatting

* Fix generated code formatting
2023-04-13 16:07:43 +02:00

156 lines
5.3 KiB
TypeScript

import { css } from '@emotion/css';
import { isEmpty } from 'lodash';
import React, { useEffect } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { StoreState } from 'app/types';
import ConfigureAuthCTA from './components/ConfigureAuthCTA';
import { ProviderCard } from './components/ProviderCard';
import { loadSettings } from './state/actions';
import { filterAuthSettings, getProviderUrl } from './utils';
import { getRegisteredAuthProviders } from '.';
interface OwnProps {}
export type Props = OwnProps & ConnectedProps<typeof connector>;
function mapStateToProps(state: StoreState) {
const { settings, isLoading, providerStatuses } = state.authConfig;
return {
settings,
isLoading,
providerStatuses,
};
}
const mapDispatchToProps = {
loadSettings,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
export const AuthConfigPageUnconnected = ({
settings,
providerStatuses,
isLoading,
loadSettings,
}: Props): JSX.Element => {
const styles = useStyles2(getStyles);
useEffect(() => {
loadSettings();
}, [loadSettings]);
const authProviders = getRegisteredAuthProviders();
const enabledProviders = authProviders.filter((p) => providerStatuses[p.id]?.enabled);
const configuresProviders = authProviders.filter(
(p) => providerStatuses[p.id]?.configured && !providerStatuses[p.id]?.enabled
);
const availableProviders = authProviders.filter(
(p) => !providerStatuses[p.id]?.enabled && !providerStatuses[p.id]?.configured
);
const authSettings = filterAuthSettings(settings);
const firstAvailableProvider = availableProviders?.length ? availableProviders[0] : null;
return (
<Page navId="authentication">
<Page.Contents isLoading={isLoading}>
<h3 className={styles.sectionHeader}>Configured authentication</h3>
{!!enabledProviders?.length && (
<div className={styles.cardsContainer}>
{enabledProviders.map((provider) => (
<ProviderCard
key={provider.id}
providerId={provider.id}
displayName={provider.displayName}
authType={provider.type}
enabled={providerStatuses[provider.id]?.enabled}
configPath={provider.configPath}
/>
))}
</div>
)}
{!enabledProviders?.length && firstAvailableProvider && !isEmpty(providerStatuses) && (
<ConfigureAuthCTA
title={`You have no ${firstAvailableProvider.type} configuration created at the moment`}
buttonIcon="plus-circle"
buttonLink={getProviderUrl(firstAvailableProvider)}
buttonTitle={`Configure ${firstAvailableProvider.type}`}
description={`Important: if you have ${firstAvailableProvider.type} configuration enabled via the .ini file Grafana is using it.
Configuring ${firstAvailableProvider.type} via UI will take precedence over any configuration in the .ini file.
No changes will be written into .ini file.`}
/>
)}
{!!configuresProviders?.length && (
<div className={styles.cardsContainer}>
{configuresProviders.map((provider) => (
<ProviderCard
key={provider.id}
providerId={provider.id}
displayName={provider.displayName}
authType={provider.protocol}
enabled={providerStatuses[provider.id]?.enabled}
configPath={provider.configPath}
/>
))}
</div>
)}
<div className={styles.settingsSection}>
<h3>Settings</h3>
{authSettings && (
<table className="filter-table">
<tbody>
{Object.entries(authSettings).map(([sectionName, sectionSettings], i) => (
<React.Fragment key={`section-${i}`}>
<tr>
<td className="admin-settings-section">{sectionName}</td>
<td />
</tr>
{Object.entries(sectionSettings).map(([settingName, settingValue], j) => (
<tr key={`property-${j}`}>
<td className={styles.settingName}>{settingName}</td>
<td className={styles.settingName}>{settingValue}</td>
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
)}
</div>
</Page.Contents>
</Page>
);
};
const getStyles = (theme: GrafanaTheme2) => {
return {
cardsContainer: css`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(288px, 1fr));
gap: ${theme.spacing(3)};
margin-bottom: ${theme.spacing(3)};
margin-top: ${theme.spacing(2)};
`,
sectionHeader: css`
margin-bottom: ${theme.spacing(3)};
`,
settingsSection: css`
margin-top: ${theme.spacing(4)};
`,
settingName: css`
padding-left: 25px;
`,
settingValue: css`
white-space: break-spaces;
`,
};
};
export default connector(AuthConfigPageUnconnected);