The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/api-keys/ApiKeysPage.tsx

231 lines
6.4 KiB

import { PureComponent } from 'react';
import * as React from 'react';
import { connect, ConnectedProps } from 'react-redux';
// Utils
import { InlineField, InlineSwitch, Modal, Button, EmptyState } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { contextSrv } from 'app/core/core';
import { t } from 'app/core/internationalization';
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<typeof connector>;
interface State {
showMigrationResult: boolean;
}
export class ApiKeysPageUnconnected extends PureComponent<Props, State> {
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<HTMLInputElement>) => {
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 (
<Page {...defaultPageProps}>
<Page.Contents isLoading={!hasFetched}>
<MigrateToServiceAccountsCard onMigrate={this.onMigrateApiKeys} apikeysCount={apiKeysCount} />
{showTable ? (
<ApiKeysActionBar
searchQuery={searchQuery}
disabled={!canCreate}
onSearchChange={this.onSearchQueryChange}
/>
) : null}
<InlineField disabled={includeExpiredDisabled} label="Include expired keys">
<InlineSwitch id="showExpired" value={includeExpired} onChange={this.onIncludeExpiredChange} />
</InlineField>
{apiKeys.length > 0 ? (
<ApiKeysTable
apiKeys={apiKeys}
timeZone={timeZone}
onMigrate={this.onMigrateApiKey}
onDelete={this.onDeleteApiKey}
/>
) : (
<EmptyState variant="not-found" message={t('api-keys.empty-state.message', 'No API keys found')} />
)}
</Page.Contents>
{migrationResult && (
<MigrationSummary
visible={this.state.showMigrationResult}
data={migrationResult}
onDismiss={this.dismissModal}
/>
)}
</Page>
);
}
}
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<MigrationSummaryProps> = ({ visible, data, onDismiss }) => {
return (
<Modal title="Migration summary" isOpen={visible} closeOnBackdropClick={true} onDismiss={onDismiss}>
{data.failedApikeyIDs.length === 0 && (
<div style={styles.migrationSummary}>
<p>Migration Successful!</p>
<p>
<strong>Total: </strong>
{data.total}
</p>
<p>
<strong>Migrated: </strong>
{data.migrated}
</p>
</div>
)}
{data.failedApikeyIDs.length !== 0 && (
<div style={styles.migrationSummary}>
<p>
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.
</p>
<hr />
<p>
<strong>Total: </strong>
{data.total}
</p>
<p>
<strong>Migrated: </strong>
{data.migrated}
</p>
<p>
<strong>Failed: </strong>
{data.failed}
</p>
<p>
<strong>Failed Api Key IDs: </strong>
{data.failedApikeyIDs.join(', ')}
</p>
<p>
<strong>Failed Details: </strong>
{data.failedDetails.join(', ')}
</p>
</div>
)}
<Modal.ButtonRow>
<Button variant="secondary" onClick={onDismiss}>
Close
</Button>
</Modal.ButtonRow>
</Modal>
);
};
const ApiKeysPage = connector(ApiKeysPageUnconnected);
export default ApiKeysPage;