mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Service accounts: polish UI and refactoring (#47269)
* no other tab has title * made Add service button not visable and upper right * renaming and added space between token number * aligned button (:css:) * refactor: out component listitem * unused import
This commit is contained in:
@@ -31,7 +31,7 @@ const ServiceAccountCreatePage: React.FC<ServiceAccountCreatePageProps> = ({ nav
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
<Page.Contents>
|
||||
<h1>Add new service account</h1>
|
||||
<h1>Create service account</h1>
|
||||
<Form onSubmit={onSubmit} validateOn="onBlur">
|
||||
{({ register, errors }) => {
|
||||
return (
|
||||
@@ -44,7 +44,7 @@ const ServiceAccountCreatePage: React.FC<ServiceAccountCreatePageProps> = ({ nav
|
||||
>
|
||||
<Input id="display-name-input" {...register('name', { required: true })} />
|
||||
</Field>
|
||||
<Button type="submit">Create Service account</Button>
|
||||
<Button type="submit">Create</Button>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from './state/actions';
|
||||
import { ServiceAccountTokensTable } from './ServiceAccountTokensTable';
|
||||
import { getTimeZone, NavModel } from '@grafana/data';
|
||||
import { Button, VerticalGroup } from '@grafana/ui';
|
||||
import { Button } from '@grafana/ui';
|
||||
import { CreateTokenModal } from './CreateTokenModal';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
|
||||
@@ -110,13 +110,15 @@ const ServiceAccountPageUnconnected = ({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<VerticalGroup spacing="md">
|
||||
<div className="page-action-bar" style={{ justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<h3 className="page-heading" style={{ marginBottom: '0px' }}>
|
||||
Tokens
|
||||
</h3>
|
||||
<Button onClick={() => setIsModalOpen(true)}>Add token</Button>
|
||||
<h3 className="page-heading">Tokens</h3>
|
||||
{tokens && (
|
||||
<ServiceAccountTokensTable tokens={tokens} timeZone={timezone} onDelete={onDeleteServiceAccountToken} />
|
||||
)}
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
{tokens && (
|
||||
<ServiceAccountTokensTable tokens={tokens} timeZone={timezone} onDelete={onDeleteServiceAccountToken} />
|
||||
)}
|
||||
<CreateTokenModal isOpen={isModalOpen} token={newToken} onCreateToken={onCreateToken} onClose={onModalClose} />
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
|
||||
@@ -87,7 +87,7 @@ export function ServiceAccountProfile({
|
||||
builtInRoles={builtInRoles}
|
||||
roleOptions={roleOptions}
|
||||
/>
|
||||
<ServiceAccountProfileRow label="Teams" value={serviceAccount.teams.join(', ')} />
|
||||
{/* <ServiceAccountProfileRow label="Teams" value={serviceAccount.teams.join(', ')} /> */}
|
||||
<ServiceAccountProfileRow
|
||||
label="Creation date"
|
||||
value={dateTimeFormat(serviceAccount.createdAt, { timeZone })}
|
||||
|
||||
127
public/app/features/serviceaccounts/ServiceAccountsListItem.tsx
Normal file
127
public/app/features/serviceaccounts/ServiceAccountsListItem.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import { cx } from '@emotion/css';
|
||||
import { OrgRole } from '@grafana/data';
|
||||
import { Button, Icon, useStyles2 } from '@grafana/ui';
|
||||
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { AccessControlAction, Role, ServiceAccountDTO } from 'app/types';
|
||||
import React, { memo } from 'react';
|
||||
import { OrgRolePicker } from '../admin/OrgRolePicker';
|
||||
import { getStyles } from './ServiceAccountsListPage';
|
||||
|
||||
type ServiceAccountListItemProps = {
|
||||
serviceAccount: ServiceAccountDTO;
|
||||
onRoleChange: (role: OrgRole, serviceAccount: ServiceAccountDTO) => void;
|
||||
roleOptions: Role[];
|
||||
builtInRoles: Record<string, Role[]>;
|
||||
onSetToRemove: (serviceAccount: ServiceAccountDTO) => void;
|
||||
};
|
||||
|
||||
const getServiceAccountsAriaLabel = (name: string) => {
|
||||
return `Edit service account's ${name} details`;
|
||||
};
|
||||
const getServiceAccountsEnabledStatus = (disabled: boolean) => {
|
||||
return disabled ? 'Disabled' : 'Enabled';
|
||||
};
|
||||
|
||||
const ServiceAccountListItem = memo(
|
||||
({ serviceAccount, onRoleChange, roleOptions, builtInRoles, onSetToRemove }: ServiceAccountListItemProps) => {
|
||||
const editUrl = `org/serviceaccounts/${serviceAccount.id}`;
|
||||
const styles = useStyles2(getStyles);
|
||||
const canUpdateRole = contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsWrite, serviceAccount);
|
||||
const rolePickerDisabled = !canUpdateRole;
|
||||
|
||||
return (
|
||||
<tr key={serviceAccount.id}>
|
||||
<td className="width-4 text-center link-td">
|
||||
<a href={editUrl} aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}>
|
||||
<img
|
||||
className="filter-table__avatar"
|
||||
src={serviceAccount.avatarUrl}
|
||||
alt={`Avatar for user ${serviceAccount.name}`}
|
||||
/>
|
||||
</a>
|
||||
</td>
|
||||
<td className="link-td max-width-10">
|
||||
<a
|
||||
className="ellipsis"
|
||||
href={editUrl}
|
||||
title={serviceAccount.name}
|
||||
aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}
|
||||
>
|
||||
{serviceAccount.name}
|
||||
</a>
|
||||
</td>
|
||||
<td className="link-td max-width-10">
|
||||
<a
|
||||
className="ellipsis"
|
||||
href={editUrl}
|
||||
title={serviceAccount.login}
|
||||
aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}
|
||||
>
|
||||
{serviceAccount.login}
|
||||
</a>
|
||||
</td>
|
||||
<td className={cx('link-td', styles.iconRow)}>
|
||||
{contextSrv.licensedAccessControlEnabled() ? (
|
||||
<UserRolePicker
|
||||
userId={serviceAccount.id}
|
||||
orgId={serviceAccount.orgId}
|
||||
builtInRole={serviceAccount.role}
|
||||
onBuiltinRoleChange={(newRole) => onRoleChange(newRole, serviceAccount)}
|
||||
roleOptions={roleOptions}
|
||||
builtInRoles={builtInRoles}
|
||||
disabled={rolePickerDisabled}
|
||||
/>
|
||||
) : (
|
||||
<OrgRolePicker
|
||||
aria-label="Role"
|
||||
value={serviceAccount.role}
|
||||
disabled={!canUpdateRole}
|
||||
onChange={(newRole) => onRoleChange(newRole, serviceAccount)}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className="link-td max-width-10">
|
||||
<a
|
||||
className="ellipsis"
|
||||
href={editUrl}
|
||||
title={getServiceAccountsEnabledStatus(serviceAccount.isDisabled)}
|
||||
aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}
|
||||
>
|
||||
{getServiceAccountsEnabledStatus(serviceAccount.isDisabled)}
|
||||
</a>
|
||||
</td>
|
||||
<td className="link-td max-width-10">
|
||||
<a
|
||||
className="ellipsis"
|
||||
href={editUrl}
|
||||
title="Tokens"
|
||||
aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}
|
||||
>
|
||||
<span>
|
||||
<Icon name={'key-skeleton-alt'}></Icon>
|
||||
</span>
|
||||
|
||||
{serviceAccount.tokens}
|
||||
</a>
|
||||
</td>
|
||||
{contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsDelete, serviceAccount) && (
|
||||
<td>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
onSetToRemove(serviceAccount);
|
||||
}}
|
||||
icon="times"
|
||||
aria-label="Delete service account"
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
);
|
||||
ServiceAccountListItem.displayName = 'ServiceAccountListItem';
|
||||
|
||||
export default ServiceAccountListItem;
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { memo, useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import { Button, ConfirmModal, FilterInput, Icon, LinkButton, RadioButtonGroup, useStyles2 } from '@grafana/ui';
|
||||
import { ConfirmModal, FilterInput, LinkButton, RadioButtonGroup, useStyles2 } from '@grafana/ui';
|
||||
import { css, cx } from '@emotion/css';
|
||||
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import { StoreState, ServiceAccountDTO, AccessControlAction, Role } from 'app/types';
|
||||
import { StoreState, ServiceAccountDTO, AccessControlAction } from 'app/types';
|
||||
import {
|
||||
changeFilter,
|
||||
changeQuery,
|
||||
@@ -18,10 +18,9 @@ import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
||||
import { GrafanaTheme2, OrgRole } from '@grafana/data';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
||||
import { OrgRolePicker } from '../admin/OrgRolePicker';
|
||||
import pluralize from 'pluralize';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import ServiceAccountListItem from './ServiceAccountsListItem';
|
||||
|
||||
interface OwnProps {}
|
||||
|
||||
@@ -79,7 +78,6 @@ const ServiceAccountsListPage = ({
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
<Page.Contents>
|
||||
<h2>Service accounts</h2>
|
||||
<div className="page-action-bar" style={{ justifyContent: 'flex-end' }}>
|
||||
<FilterInput
|
||||
placeholder="Search service account by name."
|
||||
@@ -96,6 +94,11 @@ const ServiceAccountsListPage = ({
|
||||
value={filters.find((f) => f.name === 'expiredTokens')?.value}
|
||||
className={styles.filter}
|
||||
/>
|
||||
{serviceAccounts.length !== 0 && contextSrv.hasPermission(AccessControlAction.ServiceAccountsCreate) && (
|
||||
<LinkButton href="org/serviceaccounts/create" variant="primary">
|
||||
Add service account
|
||||
</LinkButton>
|
||||
)}
|
||||
</div>
|
||||
{isLoading && <PageLoader />}
|
||||
{!isLoading && serviceAccounts.length === 0 && (
|
||||
@@ -104,7 +107,7 @@ const ServiceAccountsListPage = ({
|
||||
title="You haven't created any service accounts yet."
|
||||
buttonIcon="key-skeleton-alt"
|
||||
buttonLink="org/serviceaccounts/create"
|
||||
buttonTitle=" New service account"
|
||||
buttonTitle="Add service account"
|
||||
buttonDisabled={!contextSrv.hasPermission(AccessControlAction.ServiceAccountsCreate)}
|
||||
proTip="Remember, you can provide specific permissions for API access to other applications."
|
||||
proTipLink=""
|
||||
@@ -115,11 +118,6 @@ const ServiceAccountsListPage = ({
|
||||
)}
|
||||
{!isLoading && serviceAccounts.length !== 0 && (
|
||||
<>
|
||||
{contextSrv.hasPermission(AccessControlAction.ServiceAccountsCreate) && (
|
||||
<LinkButton href="org/serviceaccounts/create" variant="primary">
|
||||
New service account
|
||||
</LinkButton>
|
||||
)}
|
||||
<div className={cx(styles.table, 'admin-list-table')}>
|
||||
<table className="filter-table form-inline filter-table--hover">
|
||||
<thead>
|
||||
@@ -179,122 +177,7 @@ const ServiceAccountsListPage = ({
|
||||
);
|
||||
};
|
||||
|
||||
type ServiceAccountListItemProps = {
|
||||
serviceAccount: ServiceAccountDTO;
|
||||
onRoleChange: (role: OrgRole, serviceAccount: ServiceAccountDTO) => void;
|
||||
roleOptions: Role[];
|
||||
builtInRoles: Record<string, Role[]>;
|
||||
onSetToRemove: (serviceAccount: ServiceAccountDTO) => void;
|
||||
};
|
||||
|
||||
const getServiceAccountsAriaLabel = (name: string) => {
|
||||
return `Edit service account's ${name} details`;
|
||||
};
|
||||
const getServiceAccountsEnabledStatus = (disabled: boolean) => {
|
||||
return disabled ? 'Disabled' : 'Enabled';
|
||||
};
|
||||
|
||||
const ServiceAccountListItem = memo(
|
||||
({ serviceAccount, onRoleChange, roleOptions, builtInRoles, onSetToRemove }: ServiceAccountListItemProps) => {
|
||||
const editUrl = `org/serviceaccounts/${serviceAccount.id}`;
|
||||
const styles = useStyles2(getStyles);
|
||||
const canUpdateRole = contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsWrite, serviceAccount);
|
||||
const rolePickerDisabled = !canUpdateRole;
|
||||
|
||||
return (
|
||||
<tr key={serviceAccount.id}>
|
||||
<td className="width-4 text-center link-td">
|
||||
<a href={editUrl} aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}>
|
||||
<img
|
||||
className="filter-table__avatar"
|
||||
src={serviceAccount.avatarUrl}
|
||||
alt={`Avatar for user ${serviceAccount.name}`}
|
||||
/>
|
||||
</a>
|
||||
</td>
|
||||
<td className="link-td max-width-10">
|
||||
<a
|
||||
className="ellipsis"
|
||||
href={editUrl}
|
||||
title={serviceAccount.name}
|
||||
aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}
|
||||
>
|
||||
{serviceAccount.name}
|
||||
</a>
|
||||
</td>
|
||||
<td className="link-td max-width-10">
|
||||
<a
|
||||
className="ellipsis"
|
||||
href={editUrl}
|
||||
title={serviceAccount.login}
|
||||
aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}
|
||||
>
|
||||
{serviceAccount.login}
|
||||
</a>
|
||||
</td>
|
||||
<td className={cx('link-td', styles.iconRow)}>
|
||||
{contextSrv.licensedAccessControlEnabled() ? (
|
||||
<UserRolePicker
|
||||
userId={serviceAccount.id}
|
||||
orgId={serviceAccount.orgId}
|
||||
builtInRole={serviceAccount.role}
|
||||
onBuiltinRoleChange={(newRole) => onRoleChange(newRole, serviceAccount)}
|
||||
roleOptions={roleOptions}
|
||||
builtInRoles={builtInRoles}
|
||||
disabled={rolePickerDisabled}
|
||||
/>
|
||||
) : (
|
||||
<OrgRolePicker
|
||||
aria-label="Role"
|
||||
value={serviceAccount.role}
|
||||
disabled={!canUpdateRole}
|
||||
onChange={(newRole) => onRoleChange(newRole, serviceAccount)}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className="link-td max-width-10">
|
||||
<a
|
||||
className="ellipsis"
|
||||
href={editUrl}
|
||||
title={getServiceAccountsEnabledStatus(serviceAccount.isDisabled)}
|
||||
aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}
|
||||
>
|
||||
{getServiceAccountsEnabledStatus(serviceAccount.isDisabled)}
|
||||
</a>
|
||||
</td>
|
||||
<td className="link-td max-width-10">
|
||||
<a
|
||||
className="ellipsis"
|
||||
href={editUrl}
|
||||
title="Tokens"
|
||||
aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}
|
||||
>
|
||||
<span>
|
||||
<Icon name={'key-skeleton-alt'}></Icon>
|
||||
</span>
|
||||
{serviceAccount.tokens}
|
||||
</a>
|
||||
</td>
|
||||
{contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsDelete, serviceAccount) && (
|
||||
<td>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
onSetToRemove(serviceAccount);
|
||||
}}
|
||||
icon="times"
|
||||
aria-label="Delete service account"
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
);
|
||||
ServiceAccountListItem.displayName = 'ServiceAccountListItem';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
export const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
table: css`
|
||||
margin-top: ${theme.spacing(3)};
|
||||
|
||||
@@ -155,7 +155,7 @@ export function changePage(page: number): ThunkResult<void> {
|
||||
}
|
||||
|
||||
export function deleteServiceAccount(serviceAccountId: number): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
return async () => {
|
||||
await getBackendSrv().delete(`${BASE_URL}/${serviceAccountId}`);
|
||||
locationService.push('/org/serviceaccounts');
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user