diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 505b99f5004..1cfcde48ad3 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -496,7 +496,6 @@ playwright.config.ts @grafana/plugins-platform-frontend
/public/app/features/actions/ @grafana/dataviz-squad
/public/app/features/auth-config/ @grafana/identity-squad
/public/app/features/annotations/ @grafana/dashboards-squad
-/public/app/features/api-keys/ @grafana/identity-squad
/public/app/features/canvas/ @grafana/dataviz-squad
/public/app/features/geo/ @grafana/dataviz-squad
/public/app/features/visualization/data-hover/ @grafana/dataviz-squad
diff --git a/public/app/core/reducers/root.ts b/public/app/core/reducers/root.ts
index 99877d65c90..4a545d2c350 100644
--- a/public/app/core/reducers/root.ts
+++ b/public/app/core/reducers/root.ts
@@ -4,7 +4,6 @@ import { AnyAction, combineReducers } from 'redux';
import sharedReducers from 'app/core/reducers';
import ldapReducers from 'app/features/admin/state/reducers';
import alertingReducers from 'app/features/alerting/state/reducers';
-import apiKeysReducers from 'app/features/api-keys/state/reducers';
import authConfigReducers from 'app/features/auth-config/state/reducers';
import { browseDashboardsAPI } from 'app/features/browse-dashboards/api/browseDashboardsAPI';
import browseDashboardsReducers from 'app/features/browse-dashboards/state/slice';
@@ -42,7 +41,6 @@ const rootReducers = {
...sharedReducers,
...alertingReducers,
...teamsReducers,
- ...apiKeysReducers,
...foldersReducers,
...dashboardReducers,
...exploreReducers,
diff --git a/public/app/core/utils/navBarItem-translations.ts b/public/app/core/utils/navBarItem-translations.ts
index d32ffcc1840..819cfa6c786 100644
--- a/public/app/core/utils/navBarItem-translations.ts
+++ b/public/app/core/utils/navBarItem-translations.ts
@@ -117,8 +117,6 @@ export function getNavTitle(navId: string | undefined) {
return t('nav.plugins.title', 'Plugins');
case 'org-settings':
return t('nav.org-settings.title', 'Default preferences');
- case 'apikeys':
- return t('nav.api-keys.title', 'API keys');
case 'serviceaccounts':
return t('nav.service-accounts.title', 'Service accounts');
case 'admin':
@@ -269,8 +267,6 @@ export function getNavSubTitle(navId: string | undefined) {
return t('nav.plugins.subtitle', 'Extend the Grafana experience with plugins');
case 'org-settings':
return t('nav.org-settings.subtitle', 'Manage preferences across an organization');
- case 'apikeys':
- return t('nav.api-keys.subtitle', 'Manage and create API keys that are used to interact with Grafana HTTP APIs');
case 'serviceaccounts':
return t('nav.service-accounts.subtitle', 'Use service accounts to run automated workloads in Grafana');
case 'groupsync':
diff --git a/public/app/features/api-keys/ApiKeysActionBar.tsx b/public/app/features/api-keys/ApiKeysActionBar.tsx
deleted file mode 100644
index e200fca4ba0..00000000000
--- a/public/app/features/api-keys/ApiKeysActionBar.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { useTranslate } from '@grafana/i18n';
-import { FilterInput, InlineField } from '@grafana/ui';
-
-interface Props {
- searchQuery: string;
- disabled: boolean;
- onSearchChange: (value: string) => void;
-}
-
-export const ApiKeysActionBar = ({ searchQuery, disabled, onSearchChange }: Props) => {
- const { t } = useTranslate();
-
- return (
-
-
-
-
-
- );
-};
diff --git a/public/app/features/api-keys/ApiKeysPage.test.tsx b/public/app/features/api-keys/ApiKeysPage.test.tsx
deleted file mode 100644
index 728508a14e8..00000000000
--- a/public/app/features/api-keys/ApiKeysPage.test.tsx
+++ /dev/null
@@ -1,159 +0,0 @@
-import { render, screen, within } from '@testing-library/react';
-import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event';
-import { TestProvider } from 'test/helpers/TestProvider';
-
-import { ApiKey, OrgRole } from 'app/types';
-
-import { mockToolkitActionCreator } from '../../../test/core/redux/mocks';
-import { silenceConsoleOutput } from '../../../test/core/utils/silenceConsoleOutput';
-
-import { ApiKeysPageUnconnected, Props } from './ApiKeysPage';
-import { getMultipleMockKeys } from './__mocks__/apiKeysMock';
-import { setSearchQuery } from './state/reducers';
-
-jest.mock('app/core/core', () => {
- return {
- contextSrv: {
- hasPermission: () => true,
- hasPermissionInMetadata: () => true,
- },
- };
-});
-
-const setup = (propOverrides: Partial) => {
- const loadApiKeysMock = jest.fn();
- const deleteApiKeyMock = jest.fn();
- const migrateApiKeyMock = jest.fn();
- const addApiKeyMock = jest.fn();
- const migrateAllMock = jest.fn();
- const toggleIncludeExpiredMock = jest.fn();
- const setSearchQueryMock = mockToolkitActionCreator(setSearchQuery);
- const props: Props = {
- apiKeys: [] as ApiKey[],
- searchQuery: '',
- hasFetched: false,
- loadApiKeys: loadApiKeysMock,
- deleteApiKey: deleteApiKeyMock,
- setSearchQuery: setSearchQueryMock,
- migrateApiKey: migrateApiKeyMock,
- migrateAll: migrateAllMock,
- apiKeysCount: 0,
- timeZone: 'utc',
- includeExpired: false,
- includeExpiredDisabled: false,
- toggleIncludeExpired: toggleIncludeExpiredMock,
- canCreate: true,
- migrationResult: undefined,
- };
-
- Object.assign(props, propOverrides);
-
- const { rerender } = render(
-
-
-
- );
- return {
- rerender: (element: JSX.Element) => rerender({element} ),
- props,
- loadApiKeysMock,
- setSearchQueryMock,
- deleteApiKeyMock,
- addApiKeyMock,
- toggleIncludeExpiredMock,
- };
-};
-
-describe('ApiKeysPage', () => {
- silenceConsoleOutput();
- describe('when mounted', () => {
- it('then it should call loadApiKeys', () => {
- const { loadApiKeysMock } = setup({});
- expect(loadApiKeysMock).toHaveBeenCalledTimes(1);
- });
- });
-
- describe('when loading', () => {
- it('then should show Loading message', () => {
- setup({ hasFetched: false });
- expect(screen.getByText(/loading \.\.\./i)).toBeInTheDocument();
- });
- });
-
- describe('when there are API keys', () => {
- it('then it should render API keys table', async () => {
- const apiKeys = [
- { id: 1, name: 'First', role: OrgRole.Admin, secondsToLive: 60, expiration: '2021-01-01' },
- { id: 2, name: 'Second', role: OrgRole.Editor, secondsToLive: 60, expiration: '2021-01-02' },
- { id: 3, name: 'Third', role: OrgRole.Viewer, secondsToLive: 0, expiration: undefined },
- ];
- setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
- expect(screen.getByRole('table')).toBeInTheDocument();
- expect(screen.getAllByRole('row').length).toBe(4);
- expect(screen.getByRole('row', { name: /first admin 2021-01-01 00:00:00/i })).toBeInTheDocument();
- expect(screen.getByRole('row', { name: /second editor 2021-01-02 00:00:00/i })).toBeInTheDocument();
- expect(screen.getByRole('row', { name: /third viewer no expiration date/i })).toBeInTheDocument();
- });
- });
-
- describe('when a user toggles the Show expired toggle', () => {
- it('then it should dispatch toggleIncludeExpired', async () => {
- const apiKeys = getMultipleMockKeys(3);
- const { toggleIncludeExpiredMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
-
- await toggleShowExpired();
- expect(toggleIncludeExpiredMock).toHaveBeenCalledTimes(1);
- });
- });
-
- describe('when a user searches for an API key', () => {
- it('then it should dispatch setSearchQuery with correct parameters', async () => {
- const apiKeys = getMultipleMockKeys(3);
- const { setSearchQueryMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
-
- setSearchQueryMock.mockClear();
- expect(screen.getByPlaceholderText(/search keys/i)).toBeInTheDocument();
- await userEvent.type(screen.getByPlaceholderText(/search keys/i), 'First');
- expect(setSearchQueryMock).toHaveBeenCalledTimes(5);
- });
- });
-
- describe('when a user deletes an API key', () => {
- it('then it should dispatch deleteApi with correct parameters', async () => {
- const apiKeys = [
- { id: 1, name: 'First', role: OrgRole.Admin, secondsToLive: 60, expiration: '2021-01-01' },
- { id: 2, name: 'Second', role: OrgRole.Editor, secondsToLive: 60, expiration: '2021-01-02' },
- { id: 3, name: 'Third', role: OrgRole.Viewer, secondsToLive: 0, expiration: undefined },
- ];
- const { deleteApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
- const firstRow = screen.getByRole('row', { name: /first admin 2021-01-01 00:00:00/i });
- const secondRow = screen.getByRole('row', { name: /second editor 2021-01-02 00:00:00/i });
-
- deleteApiKeyMock.mockClear();
- expect(within(firstRow).getByLabelText('Delete API key')).toBeInTheDocument();
- await userEvent.click(within(firstRow).getByLabelText('Delete API key'));
-
- expect(within(firstRow).getByRole('button', { name: /delete$/i })).toBeInTheDocument();
- await userEvent.click(within(firstRow).getByRole('button', { name: /delete$/i }));
- expect(deleteApiKeyMock).toHaveBeenCalledTimes(1);
- expect(deleteApiKeyMock).toHaveBeenCalledWith(1);
-
- await toggleShowExpired();
-
- deleteApiKeyMock.mockClear();
- expect(within(secondRow).getByLabelText('Delete API key')).toBeInTheDocument();
- await userEvent.click(within(secondRow).getByLabelText('Delete API key'));
- expect(within(secondRow).getByRole('button', { name: /delete$/i })).toBeInTheDocument();
- await userEvent.click(within(secondRow).getByRole('button', { name: /delete$/i }), {
- pointerEventsCheck: PointerEventsCheckLevel.Never,
- });
- expect(deleteApiKeyMock).toHaveBeenCalledTimes(1);
- expect(deleteApiKeyMock).toHaveBeenCalledWith(2);
- });
- });
-});
-
-async function toggleShowExpired() {
- expect(screen.queryByLabelText(/include expired keys/i)).toBeInTheDocument();
- await userEvent.click(screen.getByLabelText(/include expired keys/i));
-}
diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx
deleted file mode 100644
index c54d7a6e26e..00000000000
--- a/public/app/features/api-keys/ApiKeysPage.tsx
+++ /dev/null
@@ -1,261 +0,0 @@
-import { PureComponent } from 'react';
-import * as React from 'react';
-import { connect, ConnectedProps } from 'react-redux';
-
-// Utils
-import { Trans } from '@grafana/i18n';
-import { t } from '@grafana/i18n/internal';
-import { InlineField, InlineSwitch, Modal, Button, EmptyState } from '@grafana/ui';
-import { Page } from 'app/core/components/Page/Page';
-import { contextSrv } from 'app/core/core';
-import { getTimeZone } from 'app/features/profile/state/selectors';
-import { AccessControlAction, ApiKey, ApikeyMigrationResult, StoreState } from 'app/types';
-
-import { ApiKeysActionBar } from './ApiKeysActionBar';
-import { ApiKeysTable } from './ApiKeysTable';
-import { MigrateToServiceAccountsCard } from './MigrateToServiceAccountsCard';
-import { deleteApiKey, migrateApiKey, migrateAll, loadApiKeys, toggleIncludeExpired } from './state/actions';
-import { setSearchQuery } from './state/reducers';
-import { getApiKeys, getApiKeysCount, getIncludeExpired, getIncludeExpiredDisabled } from './state/selectors';
-
-function mapStateToProps(state: StoreState) {
- const canCreate = contextSrv.hasPermission(AccessControlAction.ActionAPIKeysCreate);
- return {
- apiKeys: getApiKeys(state.apiKeys),
- searchQuery: state.apiKeys.searchQuery,
- apiKeysCount: getApiKeysCount(state.apiKeys),
- hasFetched: state.apiKeys.hasFetched,
- timeZone: getTimeZone(state.user),
- includeExpired: getIncludeExpired(state.apiKeys),
- includeExpiredDisabled: getIncludeExpiredDisabled(state.apiKeys),
- canCreate: canCreate,
- migrationResult: state.apiKeys.migrationResult,
- };
-}
-
-const defaultPageProps = {
- navId: 'apikeys',
-};
-
-const mapDispatchToProps = {
- loadApiKeys,
- deleteApiKey,
- migrateApiKey,
- migrateAll,
- setSearchQuery,
- toggleIncludeExpired,
-};
-
-const connector = connect(mapStateToProps, mapDispatchToProps);
-
-interface OwnProps {}
-
-export type Props = OwnProps & ConnectedProps;
-
-interface State {
- showMigrationResult: boolean;
-}
-
-export class ApiKeysPageUnconnected extends PureComponent {
- constructor(props: Props) {
- super(props);
- this.state = {
- showMigrationResult: false,
- };
- }
-
- componentDidMount() {
- this.fetchApiKeys();
- }
-
- async fetchApiKeys() {
- await this.props.loadApiKeys();
- }
-
- onDeleteApiKey = (key: ApiKey) => {
- this.props.deleteApiKey(key.id!);
- };
-
- onMigrateApiKey = (key: ApiKey) => {
- this.props.migrateApiKey(key.id!);
- };
-
- onSearchQueryChange = (value: string) => {
- this.props.setSearchQuery(value);
- };
-
- onIncludeExpiredChange = (event: React.SyntheticEvent) => {
- this.props.toggleIncludeExpired();
- };
-
- onMigrateApiKeys = async () => {
- try {
- await this.props.migrateAll();
- this.setState({
- showMigrationResult: true,
- });
- } catch (err) {
- console.error(err);
- }
- };
-
- dismissModal = async () => {
- this.setState({ showMigrationResult: false });
- };
-
- render() {
- const {
- hasFetched,
- apiKeysCount,
- apiKeys,
- searchQuery,
- timeZone,
- includeExpired,
- includeExpiredDisabled,
- canCreate,
- migrationResult,
- } = this.props;
-
- const showTable = apiKeysCount > 0;
- return (
-
-
-
- {showTable ? (
-
- ) : null}
-
-
-
- {apiKeys.length > 0 ? (
-
- ) : (
-
- )}
-
- {migrationResult && (
-
- )}
-
- );
- }
-}
-export type MigrationSummaryProps = {
- visible: boolean;
- data: ApikeyMigrationResult;
- onDismiss: () => void;
-};
-
-const styles: { [key: string]: React.CSSProperties } = {
- migrationSummary: {
- padding: '20px',
- },
- infoText: {
- color: '#007bff',
- },
- summaryDetails: {
- marginTop: '20px',
- },
- summaryParagraph: {
- margin: '10px 0',
- },
-};
-
-export const MigrationSummary: React.FC = ({ visible, data, onDismiss }) => {
- return (
-
- {data.failedApikeyIDs.length === 0 && (
-
-
- Migration Successful!
-
-
-
- Total:
- {'{{total}}'}
-
-
-
-
- Migrated:
- {'{{migrated}}'}
-
-
-
- )}
- {data.failedApikeyIDs.length !== 0 && (
-
-
-
- Migration complete! Please note, while there might be a few API keys flagged as `failed migrations`, rest
- assured, all of your API keys are fully functional and operational. Please try again or contact support.
-
-
-
-
-
- Total:
- {'{{total}}'}
-
-
-
-
- Migrated:
- {'{{migrated}}'}
-
-
-
-
- Failed:
- {'{{failed}}'}
-
-
-
-
- Failed api key IDs:
- {'{{ids}}'}
-
-
-
-
- Failed details:
- {'{{details}}'}
-
-
-
- )}
-
-
- Close
-
-
-
- );
-};
-
-const ApiKeysPage = connector(ApiKeysPageUnconnected);
-export default ApiKeysPage;
diff --git a/public/app/features/api-keys/ApiKeysTable.tsx b/public/app/features/api-keys/ApiKeysTable.tsx
deleted file mode 100644
index d758530ed6b..00000000000
--- a/public/app/features/api-keys/ApiKeysTable.tsx
+++ /dev/null
@@ -1,112 +0,0 @@
-import { css } from '@emotion/css';
-
-import { dateTimeFormat, GrafanaTheme2, TimeZone } from '@grafana/data';
-import { Trans, useTranslate } from '@grafana/i18n';
-import { Button, DeleteButton, Icon, Stack, Tooltip, useTheme2 } from '@grafana/ui';
-import { contextSrv } from 'app/core/core';
-import { AccessControlAction } from 'app/types';
-
-import { ApiKey } from '../../types';
-
-interface Props {
- apiKeys: ApiKey[];
- timeZone: TimeZone;
- onDelete: (apiKey: ApiKey) => void;
- onMigrate: (apiKey: ApiKey) => void;
-}
-
-export const ApiKeysTable = ({ apiKeys, timeZone, onDelete, onMigrate }: Props) => {
- const theme = useTheme2();
- const { t } = useTranslate();
- const styles = getStyles(theme);
-
- return (
-
-
-
-
- Name
-
-
- Role
-
-
- Expires
-
-
- Last used at
-
-
-
-
- {apiKeys.length > 0 ? (
-
- {apiKeys.map((key) => {
- const isExpired = Boolean(key.expiration && Date.now() > new Date(key.expiration).getTime());
- return (
-
- {key.name}
- {key.role}
-
- {formatDate(key.expiration, timeZone)}
- {isExpired && (
-
-
-
-
-
- )}
-
- {formatLastUsedAtDate(timeZone, key.lastUsedAt)}
-
-
- onMigrate(key)}>
-
- Migrate to service account
-
-
- onDelete(key)}
- disabled={!contextSrv.hasPermissionInMetadata(AccessControlAction.ActionAPIKeysDelete, key)}
- />
-
-
-
- );
- })}
-
- ) : null}
-
- );
-};
-
-function formatLastUsedAtDate(timeZone: TimeZone, lastUsedAt?: string): string {
- if (!lastUsedAt) {
- return 'Never';
- }
- return dateTimeFormat(lastUsedAt, { timeZone });
-}
-
-function formatDate(expiration: string | undefined, timeZone: TimeZone): string {
- if (!expiration) {
- return 'No expiration date';
- }
- return dateTimeFormat(expiration, { timeZone });
-}
-
-const getStyles = (theme: GrafanaTheme2) => ({
- tableRow: (isExpired: boolean) =>
- css({
- color: isExpired ? theme.colors.text.secondary : theme.colors.text.primary,
- }),
- tooltipContainer: css({
- marginLeft: theme.spacing(1),
- }),
-});
diff --git a/public/app/features/api-keys/MigrateToServiceAccountsCard.tsx b/public/app/features/api-keys/MigrateToServiceAccountsCard.tsx
deleted file mode 100644
index c45c33a58df..00000000000
--- a/public/app/features/api-keys/MigrateToServiceAccountsCard.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import { css } from '@emotion/css';
-import { useState } from 'react';
-
-import { GrafanaTheme2 } from '@grafana/data';
-import { Trans, useTranslate } from '@grafana/i18n';
-import { Alert, Button, ConfirmModal, useStyles2 } from '@grafana/ui';
-
-interface Props {
- onMigrate: () => void;
- apikeysCount: number;
- disabled?: boolean;
-}
-
-export const MigrateToServiceAccountsCard = ({ onMigrate, apikeysCount, disabled }: Props): JSX.Element => {
- const [isModalOpen, setIsModalOpen] = useState(false);
- const styles = useStyles2(getStyles);
- const { t } = useTranslate();
- const docsLink = (
-
-
- Find out more about the migration here.
-
-
- );
- const migrationBoxDesc = (
-
-
- Migrating all API keys will hide the API keys tab.
-
-
- );
-
- return (
- <>
- {apikeysCount > 0 && (
-
-
-
- API keys are deprecated and will be removed from Grafana on Jan 31, 2025. Each API key will be migrated
- into a service account with a token and will continue to work as they were. We encourage you to migrate
- your API keys to service accounts now. {' '}
-
-
-
- setIsModalOpen(true)}>
-
- Migrate all service accounts
-
-
- setIsModalOpen(false)}
- confirmVariant="primary"
- confirmButtonVariant="primary"
- />
-
-
- )}
- {apikeysCount === 0 && (
- <>
-
-
-
- No API keys were found. If you reload the browser, this page will not be available anymore.
-
-
-
- >
- )}
- >
- );
-};
-
-export const getStyles = (theme: GrafanaTheme2) => ({
- text: css({
- marginBottom: theme.spacing(2),
- }),
- actionRow: css({
- display: 'flex',
- alignItems: 'center',
- }),
- actionButton: css({
- marginRight: theme.spacing(2),
- }),
-});
diff --git a/public/app/features/api-keys/__mocks__/apiKeysMock.ts b/public/app/features/api-keys/__mocks__/apiKeysMock.ts
deleted file mode 100644
index c244cb6af8f..00000000000
--- a/public/app/features/api-keys/__mocks__/apiKeysMock.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { ApiKey, OrgRole } from 'app/types';
-
-export const getMultipleMockKeys = (numberOfKeys: number): ApiKey[] => {
- const keys: ApiKey[] = [];
-
- for (let i = 1; i <= numberOfKeys; i++) {
- keys.push({
- id: i,
- name: `test-${i}`,
- role: OrgRole.Viewer,
- secondsToLive: 100,
- expiration: '2019-06-04',
- });
- }
-
- return keys;
-};
-
-export const getMockKey = (): ApiKey => {
- return {
- id: 1,
- name: 'test',
- role: OrgRole.Admin,
- secondsToLive: 200,
- expiration: '2019-06-04',
- };
-};
diff --git a/public/app/features/api-keys/state/actions.ts b/public/app/features/api-keys/state/actions.ts
deleted file mode 100644
index f81a8310d48..00000000000
--- a/public/app/features/api-keys/state/actions.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { getBackendSrv } from 'app/core/services/backend_srv';
-import { ThunkResult } from 'app/types';
-
-import { apiKeysLoaded, includeExpiredToggled, isFetching, setMigrationResult } from './reducers';
-
-export function loadApiKeys(): ThunkResult {
- return async (dispatch) => {
- dispatch(isFetching());
- const [keys, keysIncludingExpired] = await Promise.all([
- getBackendSrv().get('/api/auth/keys?includeExpired=false&accesscontrol=true'),
- getBackendSrv().get('/api/auth/keys?includeExpired=true&accesscontrol=true'),
- ]);
- dispatch(apiKeysLoaded({ keys, keysIncludingExpired }));
- };
-}
-
-export function deleteApiKey(id: number): ThunkResult {
- return async (dispatch) => {
- getBackendSrv()
- .delete(`/api/auth/keys/${id}`)
- .then(() => dispatch(loadApiKeys()));
- };
-}
-
-export function migrateApiKey(id: number): ThunkResult {
- return async (dispatch) => {
- try {
- await getBackendSrv().post(`/api/serviceaccounts/migrate/${id}`);
- } finally {
- dispatch(loadApiKeys());
- }
- };
-}
-
-export function migrateAll(): ThunkResult {
- return async (dispatch) => {
- try {
- const payload = await getBackendSrv().post('/api/serviceaccounts/migrate');
- dispatch(setMigrationResult(payload));
- } finally {
- dispatch(loadApiKeys());
- }
- };
-}
-
-export function toggleIncludeExpired(): ThunkResult {
- return (dispatch) => {
- dispatch(includeExpiredToggled());
- };
-}
diff --git a/public/app/features/api-keys/state/reducers.test.ts b/public/app/features/api-keys/state/reducers.test.ts
deleted file mode 100644
index b4048dd194f..00000000000
--- a/public/app/features/api-keys/state/reducers.test.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { reducerTester } from '../../../../test/core/redux/reducerTester';
-import { ApiKeysState } from '../../../types';
-import { getMultipleMockKeys } from '../__mocks__/apiKeysMock';
-
-import {
- apiKeysLoaded,
- apiKeysReducer,
- includeExpiredToggled,
- initialApiKeysState,
- isFetching,
- setSearchQuery,
-} from './reducers';
-
-describe('API Keys reducer', () => {
- it('should set keys', () => {
- reducerTester()
- .givenReducer(apiKeysReducer, { ...initialApiKeysState })
- .whenActionIsDispatched(
- apiKeysLoaded({ keys: getMultipleMockKeys(4), keysIncludingExpired: getMultipleMockKeys(6) })
- )
- .thenStateShouldEqual({
- ...initialApiKeysState,
- keys: getMultipleMockKeys(4),
- keysIncludingExpired: getMultipleMockKeys(6),
- hasFetched: true,
- });
- });
-
- it('should set search query', () => {
- reducerTester()
- .givenReducer(apiKeysReducer, { ...initialApiKeysState })
- .whenActionIsDispatched(setSearchQuery('test query'))
- .thenStateShouldEqual({
- ...initialApiKeysState,
- searchQuery: 'test query',
- });
- });
-
- it('should toggle the includeExpired state', () => {
- reducerTester()
- .givenReducer(apiKeysReducer, { ...initialApiKeysState })
- .whenActionIsDispatched(includeExpiredToggled())
- .thenStateShouldEqual({
- ...initialApiKeysState,
- includeExpired: true,
- });
- });
-
- it('should set state when fetching', () => {
- reducerTester()
- .givenReducer(apiKeysReducer, { ...initialApiKeysState })
- .whenActionIsDispatched(isFetching())
- .thenStateShouldEqual({
- ...initialApiKeysState,
- hasFetched: false,
- });
- });
-});
diff --git a/public/app/features/api-keys/state/reducers.ts b/public/app/features/api-keys/state/reducers.ts
deleted file mode 100644
index 6e5ad5d277e..00000000000
--- a/public/app/features/api-keys/state/reducers.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { createSlice } from '@reduxjs/toolkit';
-
-import { ApiKeysState } from 'app/types';
-
-export const initialApiKeysState: ApiKeysState = {
- hasFetched: false,
- includeExpired: false,
- keys: [],
- keysIncludingExpired: [],
- searchQuery: '',
- migrationResult: {
- total: 0,
- migrated: 0,
- failed: 0,
- failedApikeyIDs: [0],
- failedDetails: [],
- },
-};
-
-const apiKeysSlice = createSlice({
- name: 'apiKeys',
- initialState: initialApiKeysState,
- reducers: {
- apiKeysLoaded: (state, action): ApiKeysState => {
- const { keys, keysIncludingExpired } = action.payload;
- const includeExpired =
- action.payload.keys.length === 0 && action.payload.keysIncludingExpired.length > 0
- ? true
- : state.includeExpired;
- return { ...state, hasFetched: true, keys, keysIncludingExpired, includeExpired };
- },
- setSearchQuery: (state, action): ApiKeysState => {
- return { ...state, searchQuery: action.payload };
- },
- includeExpiredToggled: (state): ApiKeysState => {
- return { ...state, includeExpired: !state.includeExpired };
- },
- isFetching: (state): ApiKeysState => {
- return { ...state, hasFetched: false };
- },
- setMigrationResult: (state, action): ApiKeysState => {
- return { ...state, migrationResult: action.payload };
- },
- },
-});
-
-export const { apiKeysLoaded, includeExpiredToggled, isFetching, setSearchQuery, setMigrationResult } =
- apiKeysSlice.actions;
-
-export const apiKeysReducer = apiKeysSlice.reducer;
-
-export default {
- apiKeys: apiKeysReducer,
-};
diff --git a/public/app/features/api-keys/state/selectors.test.ts b/public/app/features/api-keys/state/selectors.test.ts
deleted file mode 100644
index e1ba2f4d0f9..00000000000
--- a/public/app/features/api-keys/state/selectors.test.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-import { ApiKeysState } from 'app/types';
-
-import { getMultipleMockKeys } from '../__mocks__/apiKeysMock';
-
-import { getApiKeys, getApiKeysCount, getIncludeExpired, getIncludeExpiredDisabled } from './selectors';
-
-describe('API Keys selectors', () => {
- const mockKeys = getMultipleMockKeys(5);
- const mockKeysIncludingExpired = getMultipleMockKeys(8);
-
- describe('getApiKeysCount', () => {
- it('returns the correct count when includeExpired is false', () => {
- const mockState: ApiKeysState = {
- keys: mockKeys,
- keysIncludingExpired: mockKeysIncludingExpired,
- searchQuery: '',
- hasFetched: true,
- includeExpired: false,
- };
- const keyCount = getApiKeysCount(mockState);
- expect(keyCount).toBe(5);
- });
-
- it('returns the correct count when includeExpired is true', () => {
- const mockState: ApiKeysState = {
- keys: mockKeys,
- keysIncludingExpired: mockKeysIncludingExpired,
- searchQuery: '',
- hasFetched: true,
- includeExpired: true,
- };
- const keyCount = getApiKeysCount(mockState);
- expect(keyCount).toBe(8);
- });
- });
-
- describe('getApiKeys', () => {
- describe('when includeExpired is false', () => {
- it('should return all keys if no search query', () => {
- const mockState: ApiKeysState = {
- keys: mockKeys,
- keysIncludingExpired: mockKeysIncludingExpired,
- searchQuery: '',
- hasFetched: true,
- includeExpired: false,
- };
- const keys = getApiKeys(mockState);
- expect(keys).toEqual(mockKeys);
- });
-
- it('should filter keys if search query exists', () => {
- const mockState: ApiKeysState = {
- keys: mockKeys,
- keysIncludingExpired: mockKeysIncludingExpired,
- searchQuery: '5',
- hasFetched: true,
- includeExpired: false,
- };
- const keys = getApiKeys(mockState);
- expect(keys.length).toEqual(1);
- });
- });
-
- describe('when includeExpired is true', () => {
- it('should return all keys if no search query', () => {
- const mockState: ApiKeysState = {
- keys: mockKeys,
- keysIncludingExpired: mockKeysIncludingExpired,
- searchQuery: '',
- hasFetched: true,
- includeExpired: true,
- };
- const keys = getApiKeys(mockState);
- expect(keys).toEqual(mockKeysIncludingExpired);
- });
-
- it('should filter keys if search query exists', () => {
- const mockState: ApiKeysState = {
- keys: mockKeys,
- keysIncludingExpired: mockKeysIncludingExpired,
- searchQuery: '5',
- hasFetched: true,
- includeExpired: true,
- };
- const keys = getApiKeys(mockState);
- expect(keys.length).toEqual(1);
- });
- });
- });
-
- describe('getIncludeExpired', () => {
- it('returns true if includeExpired is true', () => {
- const mockState: ApiKeysState = {
- keys: mockKeys,
- keysIncludingExpired: mockKeysIncludingExpired,
- searchQuery: '',
- hasFetched: true,
- includeExpired: true,
- };
- const includeExpired = getIncludeExpired(mockState);
- expect(includeExpired).toBe(true);
- });
-
- it('returns false if includeExpired is false', () => {
- const mockState: ApiKeysState = {
- keys: mockKeys,
- keysIncludingExpired: mockKeysIncludingExpired,
- searchQuery: '',
- hasFetched: true,
- includeExpired: false,
- };
- const includeExpired = getIncludeExpired(mockState);
- expect(includeExpired).toBe(false);
- });
- });
-
- describe('getIncludeExpiredDisabled', () => {
- it('returns true if there are no active keys but there are expired keys', () => {
- const mockState: ApiKeysState = {
- keys: [],
- keysIncludingExpired: mockKeysIncludingExpired,
- searchQuery: '',
- hasFetched: true,
- includeExpired: true,
- };
- const includeExpiredDisabled = getIncludeExpiredDisabled(mockState);
- expect(includeExpiredDisabled).toBe(true);
- });
-
- it('returns false otherwise', () => {
- const mockState: ApiKeysState = {
- keys: mockKeys,
- keysIncludingExpired: mockKeysIncludingExpired,
- searchQuery: '',
- hasFetched: true,
- includeExpired: false,
- };
- const includeExpiredDisabled = getIncludeExpired(mockState);
- expect(includeExpiredDisabled).toBe(false);
- });
- });
-});
diff --git a/public/app/features/api-keys/state/selectors.ts b/public/app/features/api-keys/state/selectors.ts
deleted file mode 100644
index df05e856253..00000000000
--- a/public/app/features/api-keys/state/selectors.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { ApiKeysState } from 'app/types';
-
-export const getApiKeysCount = (state: ApiKeysState) =>
- state.includeExpired ? state.keysIncludingExpired.length : state.keys.length;
-
-export const getApiKeys = (state: ApiKeysState) => {
- const regex = RegExp(state.searchQuery, 'i');
- const keysToFilter = state.includeExpired ? state.keysIncludingExpired : state.keys;
-
- return keysToFilter.filter((key) => {
- return regex.test(key.name) || regex.test(key.role);
- });
-};
-
-export const getIncludeExpired = (state: ApiKeysState) => state.includeExpired;
-
-export const getIncludeExpiredDisabled = (state: ApiKeysState) =>
- state.keys.length === 0 && state.keysIncludingExpired.length > 0;
diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx
index 90b70ca5a9c..252b8703bd4 100644
--- a/public/app/routes/routes.tsx
+++ b/public/app/routes/routes.tsx
@@ -248,13 +248,6 @@ export function getAppRoutes(): RouteDescriptor[] {
() => import(/* webpackChunkName: "UserInvitePage" */ 'app/features/org/UserInvitePage')
),
},
- {
- path: '/org/apikeys',
- roles: () => contextSrv.evaluatePermission([AccessControlAction.ActionAPIKeysRead]),
- component: SafeDynamicImport(
- () => import(/* webpackChunkName: "ApiKeysPage" */ 'app/features/api-keys/ApiKeysPage')
- ),
- },
{
path: '/org/serviceaccounts',
roles: () =>
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index 5e7ab6e9e0c..1ad2e74501d 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -2964,51 +2964,6 @@
"title-query-result": "Query result"
}
},
- "api-keys": {
- "api-keys-action-bar": {
- "placeholder-search-keys": "Search keys"
- },
- "api-keys-page-unconnected": {
- "label-include-expired-keys": "Include expired keys"
- },
- "api-keys-table": {
- "aria-label-delete-api-key": "Delete API key",
- "content-this-api-key-has-expired": "This API key has expired.",
- "expires": "Expires",
- "last-used-at": "Last used at",
- "migrate-to-service-account": "Migrate to service account",
- "name": "Name",
- "role": "Role"
- },
- "empty-state": {
- "message": "No API keys found"
- },
- "migrate-to-service-accounts-card": {
- "body-no-api-keys-found": "No API keys were found. If you reload the browser, this page will not be available anymore.",
- "body-switch-service-accounts": "API keys are deprecated and will be removed from Grafana on Jan 31, 2025. Each API key will be migrated into a service account with a token and will continue to work as they were. We encourage you to migrate your API keys to service accounts now. ",
- "docs-link": {
- "about-migration": "Find out more about the migration here."
- },
- "migrate-all-service-accounts": "Migrate all service accounts",
- "migration-box-desc": {
- "migrating": "Migrating all API keys will hide the API keys tab."
- },
- "modal-title": "Migrate API keys to service accounts",
- "title-no-api-keys-found": "No API keys found",
- "title-switch-service-accounts": "Switch from API keys to service accounts"
- },
- "migration-summary": {
- "close": "Close",
- "failed": "<0>Failed: 0>{{failed}}",
- "failed-details": "<0>Failed details: 0>{{details}}",
- "failed-ids": "<0>Failed api key IDs: 0>{{ids}}",
- "migrated": "<0>Migrated: 0>{{migrated}}",
- "migration-complete": "Migration complete! Please note, while there might be a few API keys flagged as `failed migrations`, rest assured, all of your API keys are fully functional and operational. Please try again or contact support.",
- "migration-successful": "Migration Successful!",
- "title-migration-summary": "Migration summary",
- "total": "<0>Total: 0>{{total}}"
- }
- },
"app-chrome": {
"skip-content-button": "Skip to main content",
"top-bar": {
@@ -8162,10 +8117,6 @@
"subtitle": "See recently deleted alert rules",
"title": "Recently deleted"
},
- "api-keys": {
- "subtitle": "Manage and create API keys that are used to interact with Grafana HTTP APIs",
- "title": "API keys"
- },
"application": {
"title": "Application"
},