import { css, cx } from '@emotion/css'; import pluralize from 'pluralize'; import React, { useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { GrafanaTheme2, OrgRole } from '@grafana/data'; import { ConfirmModal, FilterInput, LinkButton, RadioButtonGroup, useStyles2 } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import Page from 'app/core/components/Page/Page'; import PageLoader from 'app/core/components/PageLoader/PageLoader'; import { contextSrv } from 'app/core/core'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState, ServiceAccountDTO, AccessControlAction } from 'app/types'; import ServiceAccountListItem from './ServiceAccountsListItem'; import { changeFilter, changeQuery, fetchACOptions, fetchServiceAccounts, removeServiceAccount, updateServiceAccount, setServiceAccountToRemove, } from './state/actions'; interface OwnProps {} type Props = OwnProps & ConnectedProps; function mapStateToProps(state: StoreState) { return { navModel: getNavModel(state.navIndex, 'serviceaccounts'), ...state.serviceAccounts, }; } const mapDispatchToProps = { fetchServiceAccounts, fetchACOptions, updateServiceAccount, removeServiceAccount, setServiceAccountToRemove, changeFilter, changeQuery, }; const connector = connect(mapStateToProps, mapDispatchToProps); const ServiceAccountsListPage = ({ fetchServiceAccounts, removeServiceAccount, fetchACOptions, updateServiceAccount, setServiceAccountToRemove, navModel, serviceAccounts, isLoading, roleOptions, builtInRoles, changeFilter, changeQuery, query, filters, serviceAccountToRemove, }: Props): JSX.Element => { const styles = useStyles2(getStyles); useEffect(() => { const fetchData = async () => { await fetchServiceAccounts(); if (contextSrv.licensedAccessControlEnabled()) { await fetchACOptions(); } }; fetchData(); }, [fetchServiceAccounts, fetchACOptions]); const onRoleChange = async (role: OrgRole, serviceAccount: ServiceAccountDTO) => { const updatedServiceAccount = { ...serviceAccount, role: role }; await updateServiceAccount(updatedServiceAccount); // need to refetch to display the new value in the list await fetchServiceAccounts(); if (contextSrv.licensedAccessControlEnabled()) { fetchACOptions(); } }; return (

Service accounts

changeFilter({ name: 'expiredTokens', value })} value={filters.find((f) => f.name === 'expiredTokens')?.value} className={styles.filter} /> {serviceAccounts.length !== 0 && contextSrv.hasPermission(AccessControlAction.ServiceAccountsCreate) && ( Add service account )}
{isLoading && } {!isLoading && serviceAccounts.length === 0 && ( <> )} {!isLoading && serviceAccounts.length !== 0 && ( <>
{serviceAccounts.map((serviceAccount: ServiceAccountDTO) => ( ))}
Account ID Roles Status Tokens
)} {serviceAccountToRemove && ( Are you sure you want to delete '{serviceAccountToRemove.name}' {Boolean(serviceAccountToRemove.tokens) && ` and ${serviceAccountToRemove.tokens} accompanying ${pluralize( 'token', serviceAccountToRemove.tokens )}`} ? } confirmText="Delete" title="Delete service account" onDismiss={() => { setServiceAccountToRemove(null); }} isOpen={true} onConfirm={() => { removeServiceAccount(serviceAccountToRemove.id); setServiceAccountToRemove(null); }} /> )}
); }; export const getStyles = (theme: GrafanaTheme2) => { return { table: css` margin-top: ${theme.spacing(3)}; `, filter: css` margin: 0 ${theme.spacing(1)}; `, iconRow: css` svg { margin-left: ${theme.spacing(0.5)}; } `, row: css` display: flex; align-items: center; height: 100% !important; a { padding: ${theme.spacing(0.5)} 0 !important; } `, unitTooltip: css` display: flex; flex-direction: column; `, unitItem: css` cursor: pointer; padding: ${theme.spacing(0.5)} 0; margin-right: ${theme.spacing(1)}; `, disabled: css` color: ${theme.colors.text.disabled}; `, link: css` color: inherit; cursor: pointer; text-decoration: underline; `, }; }; export default connector(ServiceAccountsListPage);