mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
ServiceAccounts: add role picker to service accounts list (#45127)
This commit is contained in:
parent
a771cbd871
commit
7b397184a0
@ -4,17 +4,17 @@ import { Icon, LinkButton, useStyles2 } from '@grafana/ui';
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
|
|
||||||
import Page from 'app/core/components/Page/Page';
|
import Page from 'app/core/components/Page/Page';
|
||||||
import { StoreState, ServiceAccountDTO, AccessControlAction } from 'app/types';
|
import { StoreState, ServiceAccountDTO, AccessControlAction, Role } from 'app/types';
|
||||||
import { loadServiceAccounts, removeServiceAccount, updateServiceAccount } from './state/actions';
|
import { fetchACOptions, loadServiceAccounts, removeServiceAccount, updateServiceAccount } from './state/actions';
|
||||||
import { getNavModel } from 'app/core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
import { getServiceAccounts, getServiceAccountsSearchPage, getServiceAccountsSearchQuery } from './state/selectors';
|
import { getServiceAccounts, getServiceAccountsSearchPage, getServiceAccountsSearchQuery } from './state/selectors';
|
||||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2, OrgRole } from '@grafana/data';
|
||||||
import { contextSrv } from 'app/core/core';
|
import { contextSrv } from 'app/core/core';
|
||||||
|
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
||||||
|
import { OrgRolePicker } from '../admin/OrgRolePicker';
|
||||||
export type Props = ConnectedProps<typeof connector>;
|
export type Props = ConnectedProps<typeof connector>;
|
||||||
|
|
||||||
export interface State {}
|
|
||||||
|
|
||||||
function mapStateToProps(state: StoreState) {
|
function mapStateToProps(state: StoreState) {
|
||||||
return {
|
return {
|
||||||
navModel: getNavModel(state.navIndex, 'serviceaccounts'),
|
navModel: getNavModel(state.navIndex, 'serviceaccounts'),
|
||||||
@ -22,23 +22,44 @@ function mapStateToProps(state: StoreState) {
|
|||||||
searchQuery: getServiceAccountsSearchQuery(state.serviceAccounts),
|
searchQuery: getServiceAccountsSearchQuery(state.serviceAccounts),
|
||||||
searchPage: getServiceAccountsSearchPage(state.serviceAccounts),
|
searchPage: getServiceAccountsSearchPage(state.serviceAccounts),
|
||||||
isLoading: state.serviceAccounts.isLoading,
|
isLoading: state.serviceAccounts.isLoading,
|
||||||
|
roleOptions: state.serviceAccounts.roleOptions,
|
||||||
|
builtInRoles: state.serviceAccounts.builtInRoles,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
loadServiceAccounts,
|
loadServiceAccounts,
|
||||||
|
fetchACOptions,
|
||||||
updateServiceAccount,
|
updateServiceAccount,
|
||||||
removeServiceAccount,
|
removeServiceAccount,
|
||||||
};
|
};
|
||||||
|
|
||||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||||
|
|
||||||
const ServiceAccountsListPage: React.FC<Props> = ({ loadServiceAccounts, navModel, serviceAccounts, isLoading }) => {
|
const ServiceAccountsListPage = ({
|
||||||
|
loadServiceAccounts,
|
||||||
|
fetchACOptions,
|
||||||
|
updateServiceAccount,
|
||||||
|
navModel,
|
||||||
|
serviceAccounts,
|
||||||
|
isLoading,
|
||||||
|
roleOptions,
|
||||||
|
builtInRoles,
|
||||||
|
}: Props) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadServiceAccounts();
|
loadServiceAccounts();
|
||||||
}, [loadServiceAccounts]);
|
if (contextSrv.accessControlEnabled()) {
|
||||||
|
fetchACOptions();
|
||||||
|
}
|
||||||
|
}, [loadServiceAccounts, fetchACOptions]);
|
||||||
|
|
||||||
|
const onRoleChange = (role: OrgRole, serviceAccount: ServiceAccountDTO) => {
|
||||||
|
const updatedServiceAccount = { ...serviceAccount, role: role };
|
||||||
|
|
||||||
|
updateServiceAccount(updatedServiceAccount);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page navModel={navModel}>
|
<Page navModel={navModel}>
|
||||||
<Page.Contents>
|
<Page.Contents>
|
||||||
@ -66,8 +87,14 @@ const ServiceAccountsListPage: React.FC<Props> = ({ loadServiceAccounts, navMode
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{serviceAccounts.map((serviceaccount: ServiceAccountDTO) => (
|
{serviceAccounts.map((serviceAccount: ServiceAccountDTO) => (
|
||||||
<ServiceAccountListItem serviceaccount={serviceaccount} key={serviceaccount.id} />
|
<ServiceAccountListItem
|
||||||
|
serviceAccount={serviceAccount}
|
||||||
|
key={serviceAccount.id}
|
||||||
|
builtInRoles={builtInRoles}
|
||||||
|
roleOptions={roleOptions}
|
||||||
|
onRoleChange={onRoleChange}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -80,78 +107,91 @@ const ServiceAccountsListPage: React.FC<Props> = ({ loadServiceAccounts, navMode
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ServiceAccountListItemProps = {
|
type ServiceAccountListItemProps = {
|
||||||
serviceaccount: ServiceAccountDTO;
|
serviceAccount: ServiceAccountDTO;
|
||||||
|
onRoleChange: (role: OrgRole, serviceAccount: ServiceAccountDTO) => void;
|
||||||
|
roleOptions: Role[];
|
||||||
|
builtInRoles: Record<string, Role[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getServiceAccountsAriaLabel = (name: string) => {
|
const getServiceAccountsAriaLabel = (name: string) => {
|
||||||
return `Edit service account's ${name} details`;
|
return `Edit service account's ${name} details`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ServiceAccountListItem = memo(({ serviceaccount }: ServiceAccountListItemProps) => {
|
const ServiceAccountListItem = memo(
|
||||||
const editUrl = `org/serviceaccounts/${serviceaccount.id}`;
|
({ serviceAccount, onRoleChange, roleOptions, builtInRoles }: ServiceAccountListItemProps) => {
|
||||||
const styles = useStyles2(getStyles);
|
const editUrl = `org/serviceAccounts/${serviceAccount.id}`;
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const canUpdateRole = contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsWrite, serviceAccount);
|
||||||
|
const rolePickerDisabled = !canUpdateRole;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={serviceaccount.id}>
|
<tr key={serviceAccount.id}>
|
||||||
<td className="width-4 text-center link-td">
|
<td className="width-4 text-center link-td">
|
||||||
<a href={editUrl} aria-label={getServiceAccountsAriaLabel(serviceaccount.name)}>
|
<a href={editUrl} aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}>
|
||||||
<img
|
<img
|
||||||
className="filter-table__avatar"
|
className="filter-table__avatar"
|
||||||
src={serviceaccount.avatarUrl}
|
src={serviceAccount.avatarUrl}
|
||||||
alt={`Avatar for user ${serviceaccount.name}`}
|
alt={`Avatar for user ${serviceAccount.name}`}
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td className="link-td max-width-10">
|
<td className="link-td max-width-10">
|
||||||
<a
|
<a
|
||||||
className="ellipsis"
|
className="ellipsis"
|
||||||
href={editUrl}
|
href={editUrl}
|
||||||
title={serviceaccount.name}
|
title={serviceAccount.name}
|
||||||
aria-label={getServiceAccountsAriaLabel(serviceaccount.name)}
|
aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}
|
||||||
>
|
>
|
||||||
{serviceaccount.name}
|
{serviceAccount.name}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td className="link-td max-width-10">
|
<td className="link-td max-width-10">
|
||||||
<a
|
<a
|
||||||
className="ellipsis"
|
className="ellipsis"
|
||||||
href={editUrl}
|
href={editUrl}
|
||||||
title={serviceaccount.login}
|
title={serviceAccount.login}
|
||||||
aria-label={getServiceAccountsAriaLabel(serviceaccount.name)}
|
aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}
|
||||||
>
|
>
|
||||||
{serviceaccount.login}
|
{serviceAccount.login}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td className={cx('link-td', styles.iconRow)}>
|
<td className={cx('link-td', styles.iconRow)}>
|
||||||
<a
|
{contextSrv.licensedAccessControlEnabled() ? (
|
||||||
className="ellipsis"
|
<UserRolePicker
|
||||||
href={editUrl}
|
userId={serviceAccount.id}
|
||||||
title={serviceaccount.name}
|
orgId={serviceAccount.orgId}
|
||||||
aria-label={getServiceAccountsAriaLabel(serviceaccount.name)}
|
builtInRole={serviceAccount.role}
|
||||||
>
|
onBuiltinRoleChange={(newRole) => onRoleChange(newRole, serviceAccount)}
|
||||||
{serviceaccount.role === 'None' ? (
|
roleOptions={roleOptions}
|
||||||
<span className={styles.disabled}>Not assigned </span>
|
builtInRoles={builtInRoles}
|
||||||
|
disabled={rolePickerDisabled}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
serviceaccount.role
|
<OrgRolePicker
|
||||||
|
aria-label="Role"
|
||||||
|
value={serviceAccount.role}
|
||||||
|
disabled={!canUpdateRole}
|
||||||
|
onChange={(newRole) => onRoleChange(newRole, serviceAccount)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</a>
|
</td>
|
||||||
</td>
|
<td className="link-td max-width-10">
|
||||||
<td className="link-td max-width-10">
|
<a
|
||||||
<a
|
className="ellipsis"
|
||||||
className="ellipsis"
|
href={editUrl}
|
||||||
href={editUrl}
|
title="tokens"
|
||||||
title="tokens"
|
aria-label={getServiceAccountsAriaLabel(serviceAccount.name)}
|
||||||
aria-label={getServiceAccountsAriaLabel(serviceaccount.name)}
|
>
|
||||||
>
|
<span>
|
||||||
<span>
|
<Icon name={'key-skeleton-alt'}></Icon>
|
||||||
<Icon name={'key-skeleton-alt'}></Icon>
|
</span>
|
||||||
</span>
|
{serviceAccount.tokens}
|
||||||
{serviceaccount.tokens}
|
</a>
|
||||||
</a>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
ServiceAccountListItem.displayName = 'ServiceAccountListItem';
|
ServiceAccountListItem.displayName = 'ServiceAccountListItem';
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => {
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
@ -1,137 +0,0 @@
|
|||||||
import React, { FC, useEffect, useState } from 'react';
|
|
||||||
import { AccessControlAction, Role, OrgServiceAccount } from 'app/types';
|
|
||||||
import { OrgRolePicker } from '../admin/OrgRolePicker';
|
|
||||||
import { Button, ConfirmModal } from '@grafana/ui';
|
|
||||||
import { OrgRole } from '@grafana/data';
|
|
||||||
import { contextSrv } from 'app/core/core';
|
|
||||||
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
|
||||||
import { fetchBuiltinRoles, fetchRoleOptions } from 'app/core/components/RolePicker/api';
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
serviceAccounts: OrgServiceAccount[];
|
|
||||||
orgId?: number;
|
|
||||||
onRoleChange: (role: OrgRole, serviceAccount: OrgServiceAccount) => void;
|
|
||||||
onRemoveServiceAccount: (serviceAccount: OrgServiceAccount) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ServiceAccountsTable: FC<Props> = (props) => {
|
|
||||||
const { serviceAccounts, orgId, onRoleChange, onRemoveServiceAccount } = props;
|
|
||||||
const canUpdateRole = contextSrv.hasPermission(AccessControlAction.OrgUsersRoleUpdate);
|
|
||||||
const canRemoveFromOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersRemove);
|
|
||||||
const rolePickerDisabled = !canUpdateRole;
|
|
||||||
|
|
||||||
const [roleOptions, setRoleOptions] = useState<Role[]>([]);
|
|
||||||
const [toRemove, setToRemove] = useState<OrgServiceAccount | null>(null);
|
|
||||||
const [builtinRoles, setBuiltinRoles] = useState<Record<string, Role[]>>({});
|
|
||||||
const [showRemoveModal, setShowRemoveModal] = useState<boolean | string>(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function fetchOptions() {
|
|
||||||
try {
|
|
||||||
if (contextSrv.hasPermission(AccessControlAction.ActionRolesList)) {
|
|
||||||
let options = await fetchRoleOptions(orgId);
|
|
||||||
setRoleOptions(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contextSrv.hasPermission(AccessControlAction.ActionBuiltinRolesList)) {
|
|
||||||
const builtInRoles = await fetchBuiltinRoles(orgId);
|
|
||||||
setBuiltinRoles(builtInRoles);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error loading options');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (contextSrv.accessControlEnabled()) {
|
|
||||||
fetchOptions();
|
|
||||||
}
|
|
||||||
}, [orgId]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<table className="filter-table form-inline">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th />
|
|
||||||
<th>Login</th>
|
|
||||||
<th>Email</th>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Seen</th>
|
|
||||||
<th>Role</th>
|
|
||||||
<th style={{ width: '34px' }} />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{serviceAccounts.map((serviceAccount, index) => {
|
|
||||||
return (
|
|
||||||
<tr key={`${serviceAccount.serviceAccountId}-${index}`}>
|
|
||||||
<td className="width-2 text-center">
|
|
||||||
<img className="filter-table__avatar" src={serviceAccount.avatarUrl} alt="serviceaccount avatar" />
|
|
||||||
</td>
|
|
||||||
<td className="max-width-6">
|
|
||||||
<span className="ellipsis" title={serviceAccount.login}>
|
|
||||||
{serviceAccount.login}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td className="max-width-5">
|
|
||||||
<span className="ellipsis" title={serviceAccount.email}>
|
|
||||||
{serviceAccount.email}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className="max-width-5">
|
|
||||||
<span className="ellipsis" title={serviceAccount.name}>
|
|
||||||
{serviceAccount.name}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td className="width-8">
|
|
||||||
{contextSrv.accessControlEnabled() ? (
|
|
||||||
<UserRolePicker
|
|
||||||
userId={serviceAccount.serviceAccountId}
|
|
||||||
orgId={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>
|
|
||||||
{canRemoveFromOrg && (
|
|
||||||
<td>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="destructive"
|
|
||||||
onClick={() => setShowRemoveModal(serviceAccount.login)}
|
|
||||||
icon="times"
|
|
||||||
aria-label="Delete serviceaccount"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
)}
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{toRemove !== null && (
|
|
||||||
<ConfirmModal
|
|
||||||
body={`Are you sure you want to delete ${toRemove.login} service account?`}
|
|
||||||
confirmText="Delete"
|
|
||||||
title="Delete"
|
|
||||||
onDismiss={() => setToRemove(null)}
|
|
||||||
isOpen={toRemove.login === showRemoveModal}
|
|
||||||
onConfirm={() => onRemoveServiceAccount(toRemove)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ServiceAccountsTable;
|
|
@ -1,9 +1,30 @@
|
|||||||
import { ThunkResult } from '../../../types';
|
import { ServiceAccountDTO, ThunkResult } from '../../../types';
|
||||||
import { getBackendSrv } from '@grafana/runtime';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
import { serviceAccountLoaded, serviceAccountsLoaded, serviceAccountTokensLoaded } from './reducers';
|
import {
|
||||||
|
acOptionsLoaded,
|
||||||
|
builtInRolesLoaded,
|
||||||
|
serviceAccountLoaded,
|
||||||
|
serviceAccountsLoaded,
|
||||||
|
serviceAccountTokensLoaded,
|
||||||
|
} from './reducers';
|
||||||
|
import { accessControlQueryParam } from 'app/core/utils/accessControl';
|
||||||
|
import { fetchBuiltinRoles, fetchRoleOptions } from 'app/core/components/RolePicker/api';
|
||||||
|
|
||||||
const BASE_URL = `/api/serviceaccounts`;
|
const BASE_URL = `/api/serviceaccounts`;
|
||||||
|
|
||||||
|
export function fetchACOptions(): ThunkResult<void> {
|
||||||
|
return async (dispatch) => {
|
||||||
|
try {
|
||||||
|
const options = await fetchRoleOptions();
|
||||||
|
dispatch(acOptionsLoaded(options));
|
||||||
|
const builtInRoles = await fetchBuiltinRoles();
|
||||||
|
dispatch(builtInRolesLoaded(builtInRoles));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function loadServiceAccount(saID: number): ThunkResult<void> {
|
export function loadServiceAccount(saID: number): ThunkResult<void> {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
@ -48,7 +69,7 @@ export function loadServiceAccountTokens(saID: number): ThunkResult<void> {
|
|||||||
export function loadServiceAccounts(): ThunkResult<void> {
|
export function loadServiceAccounts(): ThunkResult<void> {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
const response = await getBackendSrv().get(BASE_URL);
|
const response = await getBackendSrv().get(BASE_URL, accessControlQueryParam());
|
||||||
dispatch(serviceAccountsLoaded(response));
|
dispatch(serviceAccountsLoaded(response));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -56,10 +77,9 @@ export function loadServiceAccounts(): ThunkResult<void> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateServiceAccount(saID: number): ThunkResult<void> {
|
export function updateServiceAccount(serviceAccount: ServiceAccountDTO): ThunkResult<void> {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
// TODO: implement on backend
|
await getBackendSrv().patch(`/api/org/users/${serviceAccount.id}`, { role: serviceAccount.role });
|
||||||
await getBackendSrv().patch(`${BASE_URL}/${saID}`, {});
|
|
||||||
dispatch(loadServiceAccounts());
|
dispatch(loadServiceAccounts());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import { ApiKey, ServiceAccountDTO, ServiceAccountProfileState, ServiceAccountsState } from 'app/types';
|
import { ApiKey, Role, ServiceAccountDTO, ServiceAccountProfileState, ServiceAccountsState } from 'app/types';
|
||||||
|
|
||||||
export const initialState: ServiceAccountsState = {
|
export const initialState: ServiceAccountsState = {
|
||||||
serviceAccounts: [] as ServiceAccountDTO[],
|
serviceAccounts: [] as ServiceAccountDTO[],
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
searchPage: 1,
|
searchPage: 1,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
builtInRoles: {},
|
||||||
|
roleOptions: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialStateProfile: ServiceAccountProfileState = {
|
export const initialStateProfile: ServiceAccountProfileState = {
|
||||||
@ -42,11 +44,22 @@ const serviceAccountsSlice = createSlice({
|
|||||||
setServiceAccountsSearchPage: (state, action: PayloadAction<number>): ServiceAccountsState => {
|
setServiceAccountsSearchPage: (state, action: PayloadAction<number>): ServiceAccountsState => {
|
||||||
return { ...state, searchPage: action.payload };
|
return { ...state, searchPage: action.payload };
|
||||||
},
|
},
|
||||||
|
acOptionsLoaded: (state, action: PayloadAction<Role[]>): ServiceAccountsState => {
|
||||||
|
return { ...state, roleOptions: action.payload };
|
||||||
|
},
|
||||||
|
builtInRolesLoaded: (state, action: PayloadAction<Record<string, Role[]>>): ServiceAccountsState => {
|
||||||
|
return { ...state, builtInRoles: action.payload };
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setServiceAccountsSearchQuery, setServiceAccountsSearchPage, serviceAccountsLoaded } =
|
export const {
|
||||||
serviceAccountsSlice.actions;
|
setServiceAccountsSearchQuery,
|
||||||
|
setServiceAccountsSearchPage,
|
||||||
|
serviceAccountsLoaded,
|
||||||
|
acOptionsLoaded,
|
||||||
|
builtInRolesLoaded,
|
||||||
|
} = serviceAccountsSlice.actions;
|
||||||
|
|
||||||
export const { serviceAccountLoaded, serviceAccountTokensLoaded } = serviceAccountProfileSlice.actions;
|
export const { serviceAccountLoaded, serviceAccountTokensLoaded } = serviceAccountProfileSlice.actions;
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@ export enum AccessControlAction {
|
|||||||
UsersQuotasUpdate = 'users.quotas:update',
|
UsersQuotasUpdate = 'users.quotas:update',
|
||||||
|
|
||||||
ServiceAccountsCreate = 'serviceaccounts:create',
|
ServiceAccountsCreate = 'serviceaccounts:create',
|
||||||
|
ServiceAccountsWrite = 'serviceaccounts:write',
|
||||||
|
ServiceAccountsDelete = 'serviceaccounts:delete',
|
||||||
|
|
||||||
OrgsRead = 'orgs:read',
|
OrgsRead = 'orgs:read',
|
||||||
OrgsPreferencesRead = 'orgs.preferences:read',
|
OrgsPreferencesRead = 'orgs.preferences:read',
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { WithAccessControlMetadata } from '@grafana/data';
|
import { WithAccessControlMetadata } from '@grafana/data';
|
||||||
import { ApiKey, OrgRole } from '.';
|
import { ApiKey, OrgRole, Role } from '.';
|
||||||
|
|
||||||
export interface OrgServiceAccount {
|
export interface OrgServiceAccount extends WithAccessControlMetadata {
|
||||||
serviceAccountId: number;
|
serviceAccountId: number;
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
email: string;
|
email: string;
|
||||||
@ -31,7 +31,7 @@ export interface ServiceAccountDTO extends WithAccessControlMetadata {
|
|||||||
name: string;
|
name: string;
|
||||||
login: string;
|
login: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
role: string;
|
role: OrgRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceAccountProfileState {
|
export interface ServiceAccountProfileState {
|
||||||
@ -45,4 +45,6 @@ export interface ServiceAccountsState {
|
|||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
searchPage: number;
|
searchPage: number;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
roleOptions: Role[];
|
||||||
|
builtInRoles: Record<string, Role[]>;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user