mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
ServiceAccounts: allow deleting from service account list (#45239)
* ServiceAccounts: allow deleting from service account list * Make delete confirmation more explicit
This commit is contained in:
parent
d2b9da9dde
commit
cb461d931f
@ -1,11 +1,17 @@
|
|||||||
import React, { memo, useEffect } from 'react';
|
import React, { memo, useEffect } from 'react';
|
||||||
import { connect, ConnectedProps } from 'react-redux';
|
import { connect, ConnectedProps } from 'react-redux';
|
||||||
import { Icon, LinkButton, useStyles2 } from '@grafana/ui';
|
import { Button, ConfirmModal, 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, Role } from 'app/types';
|
import { StoreState, ServiceAccountDTO, AccessControlAction, Role } from 'app/types';
|
||||||
import { fetchACOptions, loadServiceAccounts, removeServiceAccount, updateServiceAccount } from './state/actions';
|
import {
|
||||||
|
fetchACOptions,
|
||||||
|
loadServiceAccounts,
|
||||||
|
removeServiceAccount,
|
||||||
|
updateServiceAccount,
|
||||||
|
setServiceAccountToRemove,
|
||||||
|
} 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';
|
||||||
@ -13,6 +19,7 @@ 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 { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
||||||
import { OrgRolePicker } from '../admin/OrgRolePicker';
|
import { OrgRolePicker } from '../admin/OrgRolePicker';
|
||||||
|
import pluralize from 'pluralize';
|
||||||
export type Props = ConnectedProps<typeof connector>;
|
export type Props = ConnectedProps<typeof connector>;
|
||||||
|
|
||||||
function mapStateToProps(state: StoreState) {
|
function mapStateToProps(state: StoreState) {
|
||||||
@ -24,6 +31,7 @@ function mapStateToProps(state: StoreState) {
|
|||||||
isLoading: state.serviceAccounts.isLoading,
|
isLoading: state.serviceAccounts.isLoading,
|
||||||
roleOptions: state.serviceAccounts.roleOptions,
|
roleOptions: state.serviceAccounts.roleOptions,
|
||||||
builtInRoles: state.serviceAccounts.builtInRoles,
|
builtInRoles: state.serviceAccounts.builtInRoles,
|
||||||
|
toRemove: state.serviceAccounts.serviceAccountToRemove,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,21 +40,26 @@ const mapDispatchToProps = {
|
|||||||
fetchACOptions,
|
fetchACOptions,
|
||||||
updateServiceAccount,
|
updateServiceAccount,
|
||||||
removeServiceAccount,
|
removeServiceAccount,
|
||||||
|
setServiceAccountToRemove,
|
||||||
};
|
};
|
||||||
|
|
||||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||||
|
|
||||||
const ServiceAccountsListPage = ({
|
const ServiceAccountsListPage = ({
|
||||||
loadServiceAccounts,
|
loadServiceAccounts,
|
||||||
|
removeServiceAccount,
|
||||||
fetchACOptions,
|
fetchACOptions,
|
||||||
updateServiceAccount,
|
updateServiceAccount,
|
||||||
|
setServiceAccountToRemove,
|
||||||
navModel,
|
navModel,
|
||||||
serviceAccounts,
|
serviceAccounts,
|
||||||
isLoading,
|
isLoading,
|
||||||
roleOptions,
|
roleOptions,
|
||||||
builtInRoles,
|
builtInRoles,
|
||||||
|
toRemove,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadServiceAccounts();
|
loadServiceAccounts();
|
||||||
if (contextSrv.accessControlEnabled()) {
|
if (contextSrv.accessControlEnabled()) {
|
||||||
@ -84,6 +97,7 @@ const ServiceAccountsListPage = ({
|
|||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
<th>Roles</th>
|
<th>Roles</th>
|
||||||
<th>Tokens</th>
|
<th>Tokens</th>
|
||||||
|
<th style={{ width: '34px' }} />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -94,6 +108,7 @@ const ServiceAccountsListPage = ({
|
|||||||
builtInRoles={builtInRoles}
|
builtInRoles={builtInRoles}
|
||||||
roleOptions={roleOptions}
|
roleOptions={roleOptions}
|
||||||
onRoleChange={onRoleChange}
|
onRoleChange={onRoleChange}
|
||||||
|
onSetToRemove={setServiceAccountToRemove}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -101,6 +116,28 @@ const ServiceAccountsListPage = ({
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{toRemove && (
|
||||||
|
<ConfirmModal
|
||||||
|
body={
|
||||||
|
<div>
|
||||||
|
Are you sure you want to delete '{toRemove.name}'
|
||||||
|
{Boolean(toRemove.tokens) &&
|
||||||
|
` and ${toRemove.tokens} accompanying ${pluralize('token', toRemove.tokens)}`}
|
||||||
|
?
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
confirmText="Delete"
|
||||||
|
title="Delete service account"
|
||||||
|
onDismiss={() => {
|
||||||
|
setServiceAccountToRemove(null);
|
||||||
|
}}
|
||||||
|
isOpen={true}
|
||||||
|
onConfirm={() => {
|
||||||
|
removeServiceAccount(toRemove.id);
|
||||||
|
setServiceAccountToRemove(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Page.Contents>
|
</Page.Contents>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
@ -111,6 +148,7 @@ type ServiceAccountListItemProps = {
|
|||||||
onRoleChange: (role: OrgRole, serviceAccount: ServiceAccountDTO) => void;
|
onRoleChange: (role: OrgRole, serviceAccount: ServiceAccountDTO) => void;
|
||||||
roleOptions: Role[];
|
roleOptions: Role[];
|
||||||
builtInRoles: Record<string, Role[]>;
|
builtInRoles: Record<string, Role[]>;
|
||||||
|
onSetToRemove: (serviceAccount: ServiceAccountDTO) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getServiceAccountsAriaLabel = (name: string) => {
|
const getServiceAccountsAriaLabel = (name: string) => {
|
||||||
@ -118,7 +156,7 @@ const getServiceAccountsAriaLabel = (name: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ServiceAccountListItem = memo(
|
const ServiceAccountListItem = memo(
|
||||||
({ serviceAccount, onRoleChange, roleOptions, builtInRoles }: ServiceAccountListItemProps) => {
|
({ serviceAccount, onRoleChange, roleOptions, builtInRoles, onSetToRemove }: ServiceAccountListItemProps) => {
|
||||||
const editUrl = `org/serviceAccounts/${serviceAccount.id}`;
|
const editUrl = `org/serviceAccounts/${serviceAccount.id}`;
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const canUpdateRole = contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsWrite, serviceAccount);
|
const canUpdateRole = contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsWrite, serviceAccount);
|
||||||
@ -188,6 +226,19 @@ const ServiceAccountListItem = memo(
|
|||||||
{serviceAccount.tokens}
|
{serviceAccount.tokens}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
{contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsDelete, serviceAccount) && (
|
||||||
|
<td>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => {
|
||||||
|
onSetToRemove(serviceAccount);
|
||||||
|
}}
|
||||||
|
icon="times"
|
||||||
|
aria-label="Delete service account"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
serviceAccountLoaded,
|
serviceAccountLoaded,
|
||||||
serviceAccountsLoaded,
|
serviceAccountsLoaded,
|
||||||
serviceAccountTokensLoaded,
|
serviceAccountTokensLoaded,
|
||||||
|
serviceAccountToRemoveLoaded,
|
||||||
} from './reducers';
|
} from './reducers';
|
||||||
import { accessControlQueryParam } from 'app/core/utils/accessControl';
|
import { accessControlQueryParam } from 'app/core/utils/accessControl';
|
||||||
import { fetchBuiltinRoles, fetchRoleOptions } from 'app/core/components/RolePicker/api';
|
import { fetchBuiltinRoles, fetchRoleOptions } from 'app/core/components/RolePicker/api';
|
||||||
@ -25,6 +26,16 @@ export function fetchACOptions(): ThunkResult<void> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setServiceAccountToRemove(serviceAccount: ServiceAccountDTO | null): ThunkResult<void> {
|
||||||
|
return async (dispatch) => {
|
||||||
|
try {
|
||||||
|
dispatch(serviceAccountToRemoveLoaded(serviceAccount));
|
||||||
|
} 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 {
|
||||||
|
@ -9,6 +9,7 @@ export const initialState: ServiceAccountsState = {
|
|||||||
isLoading: true,
|
isLoading: true,
|
||||||
builtInRoles: {},
|
builtInRoles: {},
|
||||||
roleOptions: [],
|
roleOptions: [],
|
||||||
|
serviceAccountToRemove: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialStateProfile: ServiceAccountProfileState = {
|
export const initialStateProfile: ServiceAccountProfileState = {
|
||||||
@ -50,6 +51,9 @@ const serviceAccountsSlice = createSlice({
|
|||||||
builtInRolesLoaded: (state, action: PayloadAction<Record<string, Role[]>>): ServiceAccountsState => {
|
builtInRolesLoaded: (state, action: PayloadAction<Record<string, Role[]>>): ServiceAccountsState => {
|
||||||
return { ...state, builtInRoles: action.payload };
|
return { ...state, builtInRoles: action.payload };
|
||||||
},
|
},
|
||||||
|
serviceAccountToRemoveLoaded: (state, action: PayloadAction<ServiceAccountDTO | null>): ServiceAccountsState => {
|
||||||
|
return { ...state, serviceAccountToRemove: action.payload };
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -59,6 +63,7 @@ export const {
|
|||||||
serviceAccountsLoaded,
|
serviceAccountsLoaded,
|
||||||
acOptionsLoaded,
|
acOptionsLoaded,
|
||||||
builtInRolesLoaded,
|
builtInRolesLoaded,
|
||||||
|
serviceAccountToRemoveLoaded,
|
||||||
} = serviceAccountsSlice.actions;
|
} = serviceAccountsSlice.actions;
|
||||||
|
|
||||||
export const { serviceAccountLoaded, serviceAccountTokensLoaded } = serviceAccountProfileSlice.actions;
|
export const { serviceAccountLoaded, serviceAccountTokensLoaded } = serviceAccountProfileSlice.actions;
|
||||||
|
@ -46,5 +46,6 @@ export interface ServiceAccountsState {
|
|||||||
searchPage: number;
|
searchPage: number;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
roleOptions: Role[];
|
roleOptions: Role[];
|
||||||
|
serviceAccountToRemove: ServiceAccountDTO | null;
|
||||||
builtInRoles: Record<string, Role[]>;
|
builtInRoles: Record<string, Role[]>;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user