import React, { PureComponent } from 'react'; import ReactDOMServer from 'react-dom/server'; import { connect } from 'react-redux'; import { hot } from 'react-hot-loader'; // Utils import { ApiKey, CoreEvents, NewApiKey, OrgRole } from 'app/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { getApiKeys, getApiKeysCount } from './state/selectors'; import { addApiKey, deleteApiKey, loadApiKeys } from './state/actions'; import Page from 'app/core/components/Page/Page'; import { SlideDown } from 'app/core/components/Animations/SlideDown'; import ApiKeysAddedModal from './ApiKeysAddedModal'; import config from 'app/core/config'; import appEvents from 'app/core/app_events'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import { DeleteButton, EventsWithValidation, InlineFormLabel, LegacyForms, ValidationEvents, Icon } from '@grafana/ui'; const { Input, Switch } = LegacyForms; import { NavModel, dateTimeFormat, rangeUtil } from '@grafana/data'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; import { store } from 'app/store/store'; import { getTimeZone } from 'app/features/profile/state/selectors'; import { setSearchQuery } from './state/reducers'; const timeRangeValidationEvents: ValidationEvents = { [EventsWithValidation.onBlur]: [ { rule: (value) => { if (!value) { return true; } try { rangeUtil.intervalToSeconds(value); return true; } catch { return false; } }, errorMessage: 'Not a valid duration', }, ], }; export interface Props { navModel: NavModel; apiKeys: ApiKey[]; searchQuery: string; hasFetched: boolean; loadApiKeys: typeof loadApiKeys; deleteApiKey: typeof deleteApiKey; setSearchQuery: typeof setSearchQuery; addApiKey: typeof addApiKey; apiKeysCount: number; includeExpired: boolean; } export interface State { isAdding: boolean; newApiKey: NewApiKey; } enum ApiKeyStateProps { Name = 'name', Role = 'role', SecondsToLive = 'secondsToLive', } const initialApiKeyState = { name: '', role: OrgRole.Viewer, secondsToLive: '', }; const tooltipText = 'The api key life duration. For example 1d if your key is going to last for one day. All the supported units are: s,m,h,d,w,M,y'; export class ApiKeysPage extends PureComponent { constructor(props: Props) { super(props); this.state = { isAdding: false, newApiKey: initialApiKeyState, includeExpired: false }; } componentDidMount() { this.fetchApiKeys(); } async fetchApiKeys() { await this.props.loadApiKeys(this.state.includeExpired); } onDeleteApiKey(key: ApiKey) { this.props.deleteApiKey(key.id, this.props.includeExpired); } onSearchQueryChange = (value: string) => { this.props.setSearchQuery(value); }; onIncludeExpiredChange = (value: boolean) => { this.setState({ hasFetched: false, includeExpired: value }, this.fetchApiKeys); }; onToggleAdding = () => { this.setState({ isAdding: !this.state.isAdding }); }; onAddApiKey = async (evt: any) => { evt.preventDefault(); const openModal = (apiKey: string) => { const rootPath = window.location.origin + config.appSubUrl; const modalTemplate = ReactDOMServer.renderToString(); appEvents.emit(CoreEvents.showModal, { templateHtml: modalTemplate, }); }; // make sure that secondsToLive is number or null const secondsToLive = this.state.newApiKey['secondsToLive']; this.state.newApiKey['secondsToLive'] = secondsToLive ? rangeUtil.intervalToSeconds(secondsToLive) : null; this.props.addApiKey(this.state.newApiKey, openModal, this.props.includeExpired); this.setState((prevState: State) => { return { ...prevState, newApiKey: initialApiKeyState, isAdding: false, }; }); }; onApiKeyStateUpdate = (evt: any, prop: string) => { const value = evt.currentTarget.value; this.setState((prevState: State) => { const newApiKey: any = { ...prevState.newApiKey, }; newApiKey[prop] = value; return { ...prevState, newApiKey: newApiKey, }; }); }; renderEmptyList() { const { isAdding } = this.state; return ( <> {!isAdding && ( )} {this.renderAddApiKeyForm()} ); } formatDate(date: any, format?: string) { if (!date) { return 'No expiration date'; } const timeZone = getTimeZone(store.getState().user); return dateTimeFormat(date, { format, timeZone }); } renderAddApiKeyForm() { const { newApiKey, isAdding } = this.state; return (
Add API Key
Key name this.onApiKeyStateUpdate(evt, ApiKeyStateProps.Name)} />
Role
Time to live this.onApiKeyStateUpdate(evt, ApiKeyStateProps.SecondsToLive)} />
); } renderApiKeyList() { const { isAdding } = this.state; const { apiKeys, searchQuery, includeExpired } = this.props; return ( <>
{this.renderAddApiKeyForm()}

Existing Keys

{ // @ts-ignore this.onIncludeExpiredChange(event.target.checked); }} /> {apiKeys.length > 0 ? ( {apiKeys.map((key) => { return ( ); })} ) : null}
Name Role Expires
{key.name} {key.role} {this.formatDate(key.expiration)} this.onDeleteApiKey(key)} />
); } render() { const { hasFetched, navModel, apiKeysCount } = this.props; return ( {hasFetched && (apiKeysCount > 0 ? this.renderApiKeyList() : this.renderEmptyList())} ); } } function mapStateToProps(state: any) { return { navModel: getNavModel(state.navIndex, 'apikeys'), apiKeys: getApiKeys(state.apiKeys), searchQuery: state.apiKeys.searchQuery, includeExpired: state.includeExpired, apiKeysCount: getApiKeysCount(state.apiKeys), hasFetched: state.apiKeys.hasFetched, }; } const mapDispatchToProps = { loadApiKeys, deleteApiKey, setSearchQuery, addApiKey, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(ApiKeysPage));