Service Accounts: small typo and dto fixes (#45677)

* ServiceAccounts: respect js casing and small fixes to displayed values

* ServiceAccounts: fix typos on service account

* ServiceAccounts: fix missing orgID in service account

* ServiceAccounts: Small fixes to dtos for profile

* ServiceAccounts: use result org id

* ServiceAccounts: return value is always nil
This commit is contained in:
J Guerreiro 2022-02-22 13:58:42 +00:00 committed by GitHub
parent 737b95e9f4
commit e201b777c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 75 additions and 46 deletions

View File

@ -147,6 +147,8 @@ type OrgUserDTO struct {
Login string `json:"login"`
Role string `json:"role"`
LastSeenAt time.Time `json:"lastSeenAt"`
Updated time.Time `json:"-"`
Created time.Time `json:"-"`
LastSeenAtAge string `json:"lastSeenAtAge"`
AccessControl map[string]bool `json:"accessControl,omitempty"`
}

View File

@ -70,13 +70,15 @@ func (api *ServiceAccountsAPI) RegisterAPIEndpoints(
auth := acmiddleware.Middleware(api.accesscontrol)
api.RouterRegister.Group("/api/serviceaccounts", func(serviceAccountsRoute routing.RouteRegister) {
serviceAccountsRoute.Get("/", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeAll)), routing.Wrap(api.ListServiceAccounts))
serviceAccountsRoute.Get("/:serviceAccountId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.RetrieveServiceAccount))
serviceAccountsRoute.Post("/", auth(middleware.ReqOrgAdmin,
accesscontrol.EvalPermission(serviceaccounts.ActionCreate)), routing.Wrap(api.CreateServiceAccount))
serviceAccountsRoute.Get("/:serviceAccountId", auth(middleware.ReqOrgAdmin,
accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.RetrieveServiceAccount))
serviceAccountsRoute.Patch("/:serviceAccountId", auth(middleware.ReqOrgAdmin,
accesscontrol.EvalPermission(serviceaccounts.ActionWrite, serviceaccounts.ScopeID)), routing.Wrap(api.updateServiceAccount))
serviceAccountsRoute.Delete("/:serviceAccountId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionDelete, serviceaccounts.ScopeID)), routing.Wrap(api.DeleteServiceAccount))
serviceAccountsRoute.Post("/upgradeall", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate)), 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)), 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,
@ -92,6 +94,8 @@ func (api *ServiceAccountsAPI) CreateServiceAccount(c *models.ReqContext) respon
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "Bad request data", err)
}
cmd.OrgID = c.OrgId
user, err := api.service.CreateServiceAccount(c.Req.Context(), &cmd)
switch {
case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound):
@ -192,6 +196,9 @@ func (api *ServiceAccountsAPI) RetrieveServiceAccount(ctx *models.ReqContext) re
return response.Error(http.StatusInternalServerError, "Failed to retrieve service account", err)
}
}
serviceAccount.AvatarUrl = dtos.GetGravatarUrlWithDefault("", serviceAccount.Name)
return response.JSON(http.StatusOK, serviceAccount)
}

View File

@ -177,12 +177,16 @@ func (s *ServiceAccountsStoreImpl) RetrieveServiceAccount(ctx context.Context, o
if len(query.Result) != 1 {
return nil, serviceaccounts.ErrServiceAccountNotFound
}
saProfile := &serviceaccounts.ServiceAccountProfileDTO{
Id: query.Result[0].UserId,
Name: query.Result[0].Name,
Login: query.Result[0].Login,
Id: query.Result[0].UserId,
Name: query.Result[0].Name,
Login: query.Result[0].Login,
OrgId: query.Result[0].OrgId,
UpdatedAt: query.Result[0].Updated,
CreatedAt: query.Result[0].Created,
}
return saProfile, err
return saProfile, nil
}
func (s *ServiceAccountsStoreImpl) UpdateServiceAccount(ctx context.Context,

View File

@ -149,6 +149,8 @@ func (ss *SQLStore) GetOrgUsers(ctx context.Context, query *models.GetOrgUsersQu
"user.login",
"org_user.role",
"user.last_seen_at",
"user.created",
"user.updated",
)
sess.Asc("user.email", "user.login")

View File

@ -89,7 +89,8 @@ const ServiceAccountPageUnconnected = ({
{serviceAccount && (
<>
<ServiceAccountProfile
serviceaccount={serviceAccount}
serviceAccount={serviceAccount}
timeZone={timezone}
onServiceAccountDelete={() => {
console.log(`not implemented`);
}}

View File

@ -2,20 +2,22 @@ import React, { PureComponent, useRef, useState } from 'react';
import { ServiceAccountDTO } from 'app/types';
import { css, cx } from '@emotion/css';
import { config } from 'app/core/config';
import { GrafanaTheme } from '@grafana/data';
import { dateTimeFormat, GrafanaTheme, TimeZone } from '@grafana/data';
import { Button, ConfirmButton, ConfirmModal, Input, LegacyInputStatus, stylesFactory } from '@grafana/ui';
interface Props {
serviceaccount: ServiceAccountDTO;
serviceAccount: ServiceAccountDTO;
timeZone: TimeZone;
onServiceAccountUpdate: (serviceaccount: ServiceAccountDTO) => void;
onServiceAccountDelete: (serviceaccountId: number) => void;
onServiceAccountDisable: (serviceaccountId: number) => void;
onServiceAccountEnable: (serviceaccountId: number) => void;
onServiceAccountUpdate: (serviceAccount: ServiceAccountDTO) => void;
onServiceAccountDelete: (serviceAccountId: number) => void;
onServiceAccountDisable: (serviceAccountId: number) => void;
onServiceAccountEnable: (serviceAccountId: number) => void;
}
export function ServiceAccountProfile({
serviceaccount,
serviceAccount,
timeZone,
onServiceAccountUpdate,
onServiceAccountDelete,
onServiceAccountDisable,
@ -40,15 +42,15 @@ export function ServiceAccountProfile({
}
};
const handleServiceAccountDelete = () => onServiceAccountDelete(serviceaccount.id);
const handleServiceAccountDelete = () => onServiceAccountDelete(serviceAccount.id);
const handleServiceAccountDisable = () => onServiceAccountDisable(serviceaccount.id);
const handleServiceAccountDisable = () => onServiceAccountDisable(serviceAccount.id);
const handleServiceAccountEnable = () => onServiceAccountEnable(serviceaccount.id);
const handleServiceAccountEnable = () => onServiceAccountEnable(serviceAccount.id);
const onServiceAccountNameChange = (newValue: string) => {
onServiceAccountUpdate({
...serviceaccount,
...serviceAccount,
name: newValue,
});
};
@ -67,14 +69,17 @@ export function ServiceAccountProfile({
<tbody>
<ServiceAccountProfileRow
label="Display Name"
value={serviceaccount.name}
value={serviceAccount.name}
onChange={onServiceAccountNameChange}
/>
<ServiceAccountProfileRow label="ID" value={serviceaccount.login} />
<ServiceAccountProfileRow label="ID" value={serviceAccount.login} />
<ServiceAccountProfileRow label="Roles" value="WIP" />
<ServiceAccountProfileRow label="Teams" value="WIP" />
<ServiceAccountProfileRow label="Created by" value="WIP" />
<ServiceAccountProfileRow label="Creation date" value="WIP" />
<ServiceAccountProfileRow
label="Creation date"
value={dateTimeFormat(serviceAccount.createdAt, { timeZone })}
/>
</tbody>
</table>
</div>
@ -86,28 +91,32 @@ export function ServiceAccountProfile({
<ConfirmModal
isOpen={showDeleteModal}
title="Delete serviceaccount"
body="Are you sure you want to delete this serviceaccount?"
confirmText="Delete serviceaccount"
body="Are you sure you want to delete this service account?"
confirmText="Delete service account"
onConfirm={handleServiceAccountDelete}
onDismiss={showDeleteServiceAccountModal(false)}
/>
</>
<Button variant="secondary" onClick={handleServiceAccountEnable}>
Enable service account
</Button>
<>
<Button variant="secondary" onClick={showDisableServiceAccountModal(true)} ref={disableServiceAccountRef}>
Disable service account
{serviceAccount.isDisabled && (
<Button variant="secondary" onClick={handleServiceAccountEnable}>
Enable service account
</Button>
<ConfirmModal
isOpen={showDisableModal}
title="Disable serviceaccount"
body="Are you sure you want to disable this serviceaccount?"
confirmText="Disable serviceaccount"
onConfirm={handleServiceAccountDisable}
onDismiss={showDisableServiceAccountModal(false)}
/>
</>
)}
{!serviceAccount.isDisabled && (
<>
<Button variant="secondary" onClick={showDisableServiceAccountModal(true)} ref={disableServiceAccountRef}>
Disable service account
</Button>
<ConfirmModal
isOpen={showDisableModal}
title="Disable service account"
body="Are you sure you want to disable this service account?"
confirmText="Disable service account"
onConfirm={handleServiceAccountDisable}
onDismiss={showDisableServiceAccountModal(false)}
/>
</>
)}
</div>
</div>
</>
@ -225,14 +234,16 @@ export class ServiceAccountProfileRow extends PureComponent<
)}
</td>
<td>
<ConfirmButton
confirmText="Save"
onClick={this.onEditClick}
onConfirm={this.onSave}
onCancel={this.onCancelClick}
>
Edit
</ConfirmButton>
{this.props.onChange && (
<ConfirmButton
confirmText="Save"
onClick={this.onEditClick}
onConfirm={this.onSave}
onCancel={this.onCancelClick}
>
Edit
</ConfirmButton>
)}
</td>
</tr>
);

View File

@ -31,6 +31,8 @@ export interface ServiceAccountDTO extends WithAccessControlMetadata {
name: string;
login: string;
avatarUrl?: string;
createdAt: string;
isDisabled: boolean;
role: OrgRole;
}