import { css, cx } from '@emotion/css'; import pluralize from 'pluralize'; import React, { useEffect, useState } 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 { StoreState, ServiceAccountDTO, AccessControlAction, ServiceAccountStateFilter } from 'app/types'; import { CreateTokenModal, ServiceAccountToken } from './components/CreateTokenModal'; import ServiceAccountListItem from './components/ServiceAccountsListItem'; import { changeQuery, fetchACOptions, fetchServiceAccounts, deleteServiceAccount, updateServiceAccount, changeStateFilter, createServiceAccountToken, } from './state/actions'; interface OwnProps {} export type Props = OwnProps & ConnectedProps; function mapStateToProps(state: StoreState) { return { ...state.serviceAccounts, }; } const mapDispatchToProps = { changeQuery, fetchACOptions, fetchServiceAccounts, deleteServiceAccount, updateServiceAccount, changeStateFilter, createServiceAccountToken, }; const connector = connect(mapStateToProps, mapDispatchToProps); export const ServiceAccountsListPageUnconnected = ({ serviceAccounts, isLoading, roleOptions, query, serviceAccountStateFilter, changeQuery, fetchACOptions, fetchServiceAccounts, deleteServiceAccount, updateServiceAccount, changeStateFilter, createServiceAccountToken, }: Props): JSX.Element => { const styles = useStyles2(getStyles); const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [isRemoveModalOpen, setIsRemoveModalOpen] = useState(false); const [isDisableModalOpen, setIsDisableModalOpen] = useState(false); const [newToken, setNewToken] = useState(''); const [currentServiceAccount, setCurrentServiceAccount] = useState(null); useEffect(() => { fetchServiceAccounts({ withLoadingIndicator: true }); if (contextSrv.licensedAccessControlEnabled()) { fetchACOptions(); } }, [fetchACOptions, fetchServiceAccounts]); const noServiceAccountsCreated = serviceAccounts.length === 0 && serviceAccountStateFilter === ServiceAccountStateFilter.All && !query; const onRoleChange = async (role: OrgRole, serviceAccount: ServiceAccountDTO) => { const updatedServiceAccount = { ...serviceAccount, role: role }; updateServiceAccount(updatedServiceAccount); if (contextSrv.licensedAccessControlEnabled()) { fetchACOptions(); } }; const onQueryChange = (value: string) => { changeQuery(value); }; const onStateFilterChange = (value: ServiceAccountStateFilter) => { changeStateFilter(value); }; const onRemoveButtonClick = (serviceAccount: ServiceAccountDTO) => { setCurrentServiceAccount(serviceAccount); setIsRemoveModalOpen(true); }; const onServiceAccountRemove = async () => { if (currentServiceAccount) { deleteServiceAccount(currentServiceAccount.id); } onRemoveModalClose(); }; const onDisableButtonClick = (serviceAccount: ServiceAccountDTO) => { setCurrentServiceAccount(serviceAccount); setIsDisableModalOpen(true); }; const onDisable = () => { if (currentServiceAccount) { updateServiceAccount({ ...currentServiceAccount, isDisabled: true }); } onDisableModalClose(); }; const onEnable = (serviceAccount: ServiceAccountDTO) => { updateServiceAccount({ ...serviceAccount, isDisabled: false }); }; const onTokenAdd = (serviceAccount: ServiceAccountDTO) => { setCurrentServiceAccount(serviceAccount); setIsAddModalOpen(true); }; const onTokenCreate = async (token: ServiceAccountToken) => { if (currentServiceAccount) { createServiceAccountToken(currentServiceAccount.id, token, setNewToken); } }; const onAddModalClose = () => { setIsAddModalOpen(false); setCurrentServiceAccount(null); setNewToken(''); }; const onRemoveModalClose = () => { setIsRemoveModalOpen(false); setCurrentServiceAccount(null); }; const onDisableModalClose = () => { setIsDisableModalOpen(false); setCurrentServiceAccount(null); }; const docsLink = ( here. ); const subTitle = ( Service accounts and their tokens can be used to authenticate against the Grafana API. Find out more {docsLink} ); return (
{!noServiceAccountsCreated && contextSrv.hasPermission(AccessControlAction.ServiceAccountsCreate) && ( Add service account )}
{isLoading && } {!isLoading && noServiceAccountsCreated && ( <> )} {!isLoading && serviceAccounts.length !== 0 && ( <>
{serviceAccounts.map((serviceAccount: ServiceAccountDTO) => ( ))}
Account ID Roles Tokens
)} {currentServiceAccount && ( <> )}
); }; export const getStyles = (theme: GrafanaTheme2) => { return { table: css` margin-top: ${theme.spacing(3)}; `, filter: css` margin: 0 ${theme.spacing(1)}; `, 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; `, pageHeader: css` display: flex; margin-bottom: ${theme.spacing(2)}; `, apiKeyInfoLabel: css` margin-left: ${theme.spacing(1)}; line-height: 2.2; flex-grow: 1; color: ${theme.colors.text.secondary}; span { padding: ${theme.spacing(0.5)}; } `, filterDelimiter: css` flex-grow: 1; `, }; }; const ServiceAccountsListPage = connector(ServiceAccountsListPageUnconnected); export default ServiceAccountsListPage;