feat: creation of service account (#44913)

* feat: creation of service account

* added back button for the details page

* refactor: remove unused file, renamed fields

* oppps
This commit is contained in:
Eric Leijonmarck 2022-02-07 14:12:39 +01:00 committed by GitHub
parent a8e9369084
commit 334c29eebf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 101 additions and 15 deletions

View File

@ -71,8 +71,11 @@ func (api *ServiceAccountsAPI) CreateServiceAccount(c *models.ReqContext) respon
case err != nil:
return response.Error(http.StatusInternalServerError, "Failed to create service account", err)
}
return response.JSON(http.StatusCreated, user)
result := models.UserIdDTO{
Message: "Service account created",
Id: user.Id,
}
return response.JSON(http.StatusCreated, result)
}
func (api *ServiceAccountsAPI) DeleteServiceAccount(ctx *models.ReqContext) response.Response {

View File

@ -27,9 +27,10 @@ func NewServiceAccountsStore(store *sqlstore.SQLStore) *ServiceAccountsStoreImpl
func (s *ServiceAccountsStoreImpl) CreateServiceAccount(ctx context.Context, sa *serviceaccounts.CreateServiceaccountForm) (user *models.User, err error) {
// create a new service account - "user" with empty permissions
generatedLogin := "Service-Account-" + uuid.New().String()
cmd := models.CreateUserCommand{
Login: "Service-Account-" + uuid.New().String(),
Name: sa.Name + "-Service-Account-" + uuid.New().String(),
Login: generatedLogin,
Name: sa.Name,
OrgId: sa.OrgID,
IsServiceAccount: true,
}

View File

@ -19,8 +19,6 @@ type ServiceAccount struct {
}
type CreateServiceaccountForm struct {
OrgID int64 `json:"-"`
Name string `json:"name" binding:"Required"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
OrgID int64 `json:"-"`
Name string `json:"name" binding:"Required"`
}

View File

@ -0,0 +1,61 @@
import React, { useCallback } from 'react';
import { connect } from 'react-redux';
import { Form, Button, Input, Field } from '@grafana/ui';
import { NavModel } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { StoreState } from '../../types';
import { getNavModel } from '../../core/selectors/navModel';
import Page from 'app/core/components/Page/Page';
import { useHistory } from 'react-router-dom';
interface ServiceAccountCreatePageProps {
navModel: NavModel;
}
interface ServiceAccountDTO {
name: string;
}
const createServiceAccount = async (sa: ServiceAccountDTO) => getBackendSrv().post('/api/serviceaccounts/', sa);
const ServiceAccountCreatePage: React.FC<ServiceAccountCreatePageProps> = ({ navModel }) => {
const history = useHistory();
const onSubmit = useCallback(
async (data: ServiceAccountDTO) => {
await createServiceAccount(data);
history.push('/org/serviceaccounts/');
},
[history]
);
return (
<Page navModel={navModel}>
<Page.Contents>
<h1>Add new service account</h1>
<Form onSubmit={onSubmit} validateOn="onBlur">
{({ register, errors }) => {
return (
<>
<Field
label="Display name"
required
invalid={!!errors.name}
error={errors.name ? 'Display name is required' : undefined}
>
<Input id="display-name-input" {...register('name', { required: true })} />
</Field>
<Button type="submit">Create Service account</Button>
</>
);
}}
</Form>
</Page.Contents>
</Page>
);
};
const mapStateToProps = (state: StoreState) => ({
navModel: getNavModel(state.navIndex, 'serviceaccounts'),
});
export default connect(mapStateToProps)(ServiceAccountCreatePage);

View File

@ -58,6 +58,9 @@ export function ServiceAccountProfile({
return (
<>
<h3 className="page-heading">Service account information</h3>
<a href="org/serviceaccounts">
<Button variant="link" icon="backward" />
</a>
<div className="gf-form-group">
<div className="gf-form">
<table className="filter-table form-inline">

View File

@ -1,15 +1,16 @@
import React, { memo, useEffect } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { useStyles2 } from '@grafana/ui';
import { LinkButton, useStyles2 } from '@grafana/ui';
import { css, cx } from '@emotion/css';
import Page from 'app/core/components/Page/Page';
import { StoreState, ServiceAccountDTO } from 'app/types';
import { StoreState, ServiceAccountDTO, AccessControlAction } from 'app/types';
import { loadServiceAccounts, removeServiceAccount, updateServiceAccount } from './state/actions';
import { getNavModel } from 'app/core/selectors/navModel';
import { getServiceAccounts, getServiceAccountsSearchPage, getServiceAccountsSearchQuery } from './state/selectors';
import PageLoader from 'app/core/components/PageLoader/PageLoader';
import { GrafanaTheme2 } from '@grafana/data';
import { contextSrv } from 'app/core/core';
export type Props = ConnectedProps<typeof connector>;
export interface State {}
@ -41,6 +42,14 @@ const ServiceAccountsListPage: React.FC<Props> = ({ loadServiceAccounts, navMode
return (
<Page navModel={navModel}>
<Page.Contents>
<h2>Service accounts</h2>
<div className="page-action-bar" style={{ justifyContent: 'flex-end' }}>
{contextSrv.hasPermission(AccessControlAction.ServiceAccountsCreate) && (
<LinkButton href="org/serviceaccounts/create" variant="primary">
New service account
</LinkButton>
)}
</div>
{isLoading ? (
<PageLoader />
) : (
@ -50,7 +59,7 @@ const ServiceAccountsListPage: React.FC<Props> = ({ loadServiceAccounts, navMode
<thead>
<tr>
<th></th>
<th>Account</th>
<th>Display name</th>
<th>ID</th>
<th>Roles</th>
<th>Tokens</th>
@ -97,20 +106,20 @@ const ServiceAccountListItem = memo(({ serviceaccount }: ServiceAccountListItemP
<a
className="ellipsis"
href={editUrl}
title={serviceaccount.login}
title={serviceaccount.name}
aria-label={getServiceAccountsAriaLabel(serviceaccount.name)}
>
{serviceaccount.login}
{serviceaccount.name}
</a>
</td>
<td className="link-td max-width-10">
<a
className="ellipsis"
href={editUrl}
title={serviceaccount.name}
title={serviceaccount.login}
aria-label={getServiceAccountsAriaLabel(serviceaccount.name)}
>
{serviceaccount.name}
{serviceaccount.login}
</a>
</td>
<td className={cx('link-td', styles.iconRow)}>

View File

@ -201,6 +201,15 @@ export function getAppRoutes(): RouteDescriptor[] {
import(/* webpackChunkName: "ServiceAccountsPage" */ 'app/features/serviceaccounts/ServiceAccountsListPage')
),
},
{
path: '/org/serviceaccounts/create',
component: SafeDynamicImport(
() =>
import(
/* webpackChunkName: "ServiceAccountCreatePage" */ 'app/features/serviceaccounts/ServiceAccountCreatePage'
)
),
},
{
path: '/org/serviceaccounts/:id',
component: ServiceAccountPage,

View File

@ -25,6 +25,8 @@ export enum AccessControlAction {
UsersQuotasList = 'users.quotas:list',
UsersQuotasUpdate = 'users.quotas:update',
ServiceAccountsCreate = 'serviceaccounts:create',
OrgsRead = 'orgs:read',
OrgsPreferencesRead = 'orgs.preferences:read',
OrgsWrite = 'orgs:write',