diff --git a/pkg/services/serviceaccounts/api/api.go b/pkg/services/serviceaccounts/api/api.go index aeeaa69acac..f90fc362825 100644 --- a/pkg/services/serviceaccounts/api/api.go +++ b/pkg/services/serviceaccounts/api/api.go @@ -65,7 +65,7 @@ func (api *ServiceAccountsAPI) RegisterAPIEndpoints( serviceAccountsRoute.Delete("/:serviceAccountId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionDelete, serviceaccounts.ScopeID)), routing.Wrap(api.DeleteServiceAccount)) serviceAccountsRoute.Get("/upgrade", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate, serviceaccounts.ScopeID)), routing.Wrap(api.UpgradeServiceAccounts)) serviceAccountsRoute.Post("/convert/:keyId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate, serviceaccounts.ScopeID)), routing.Wrap(api.ConvertToServiceAccount)) - serviceAccountsRoute.Post("/", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate, serviceaccounts.ScopeID)), routing.Wrap(api.CreateServiceAccount)) + serviceAccountsRoute.Post("/", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate)), routing.Wrap(api.CreateServiceAccount)) serviceAccountsRoute.Get("/:serviceAccountId/tokens", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.ListTokens)) serviceAccountsRoute.Post("/:serviceAccountId/tokens", auth(middleware.ReqOrgAdmin, diff --git a/pkg/services/serviceaccounts/manager/roles.go b/pkg/services/serviceaccounts/manager/roles.go index a51f6c63bd3..72a51214f27 100644 --- a/pkg/services/serviceaccounts/manager/roles.go +++ b/pkg/services/serviceaccounts/manager/roles.go @@ -18,6 +18,10 @@ func RegisterRoles(ac accesscontrol.AccessControl) error { Action: serviceaccounts.ActionRead, Scope: serviceaccounts.ScopeAll, }, + { + Action: serviceaccounts.ActionWrite, + Scope: serviceaccounts.ScopeAll, + }, { Action: serviceaccounts.ActionCreate, }, diff --git a/public/app/features/serviceaccounts/ServiceAccountPage.tsx b/public/app/features/serviceaccounts/ServiceAccountPage.tsx index 2328301a965..4ec31e06009 100644 --- a/public/app/features/serviceaccounts/ServiceAccountPage.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountPage.tsx @@ -1,67 +1,89 @@ -import React, { PureComponent } from 'react'; +import React, { useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { NavModel } from '@grafana/data'; import { getNavModel } from 'app/core/selectors/navModel'; import Page from 'app/core/components/Page/Page'; import { ServiceAccountProfile } from './ServiceAccountProfile'; -import { StoreState, ServiceAccountDTO } from 'app/types'; +import { StoreState, ServiceAccountDTO, ApiKey } from 'app/types'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { loadServiceAccount } from './state/actions'; +import { deleteServiceAccountToken, loadServiceAccount, loadServiceAccountTokens } from './state/actions'; +import { ServiceAccountTokensTable } from './ServiceAccountTokensTable'; +import { getTimeZone, NavModel } from '@grafana/data'; interface OwnProps extends GrafanaRouteComponentProps<{ id: string }> { navModel: NavModel; serviceAccount?: ServiceAccountDTO; + tokens: ApiKey[]; isLoading: boolean; } -export class ServiceAccountPage extends PureComponent { - async componentDidMount() { - const { match } = this.props; - this.props.loadServiceAccount(parseInt(match.params.id, 10)); - } - - render() { - const { navModel, serviceAccount, isLoading } = this.props; - - return ( - - - {serviceAccount && ( - <> - { - console.log(`not implemented`); - }} - onServiceAccountUpdate={() => { - console.log(`not implemented`); - }} - onServiceAccountDisable={() => { - console.log(`not implemented`); - }} - onServiceAccountEnable={() => { - console.log(`not implemented`); - }} - /> - - )} - - - ); - } -} - function mapStateToProps(state: StoreState) { return { navModel: getNavModel(state.navIndex, 'serviceaccounts'), serviceAccount: state.serviceAccountProfile.serviceAccount, + tokens: state.serviceAccountProfile.tokens, isLoading: state.serviceAccountProfile.isLoading, + timezone: getTimeZone(state.user), }; } const mapDispatchToProps = { loadServiceAccount, + loadServiceAccountTokens, + deleteServiceAccountToken, }; const connector = connect(mapStateToProps, mapDispatchToProps); type Props = OwnProps & ConnectedProps; -export default connector(ServiceAccountPage); + +const ServiceAccountPageUnconnected = ({ + navModel, + match, + serviceAccount, + tokens, + timezone, + isLoading, + loadServiceAccount, + loadServiceAccountTokens, + deleteServiceAccountToken, +}: Props) => { + useEffect(() => { + const serviceAccountId = parseInt(match.params.id, 10); + loadServiceAccount(serviceAccountId); + loadServiceAccountTokens(serviceAccountId); + }, [match, loadServiceAccount, loadServiceAccountTokens]); + + const onDeleteServiceAccountToken = (key: ApiKey) => { + deleteServiceAccountToken(parseInt(match.params.id, 10), key.id!); + }; + + return ( + + + {serviceAccount && ( + <> + { + console.log(`not implemented`); + }} + onServiceAccountUpdate={() => { + console.log(`not implemented`); + }} + onServiceAccountDisable={() => { + console.log(`not implemented`); + }} + onServiceAccountEnable={() => { + console.log(`not implemented`); + }} + /> + + )} +

Tokens

+ {tokens && ( + + )} +
+
+ ); +}; + +export const ServiceAccountPage = connector(ServiceAccountPageUnconnected); diff --git a/public/app/features/serviceaccounts/ServiceAccountProfile.tsx b/public/app/features/serviceaccounts/ServiceAccountProfile.tsx index 81eafffbd3a..95787e11f73 100644 --- a/public/app/features/serviceaccounts/ServiceAccountProfile.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountProfile.tsx @@ -57,7 +57,7 @@ export function ServiceAccountProfile({ return ( <> -

Service account information

+

Information