2018-10-10 18:07:54 -05:00
|
|
|
import React, { PureComponent } from 'react';
|
2018-09-27 04:26:47 -05:00
|
|
|
import ReactDOMServer from 'react-dom/server';
|
2018-09-25 09:23:43 -05:00
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import { hot } from 'react-hot-loader';
|
2020-01-13 01:03:22 -06:00
|
|
|
// Utils
|
|
|
|
import { ApiKey, CoreEvents, NewApiKey, OrgRole } from 'app/types';
|
2019-01-17 02:01:17 -06:00
|
|
|
import { getNavModel } from 'app/core/selectors/navModel';
|
2018-10-10 18:07:54 -05:00
|
|
|
import { getApiKeys, getApiKeysCount } from './state/selectors';
|
2020-01-13 01:03:22 -06:00
|
|
|
import { addApiKey, deleteApiKey, loadApiKeys } from './state/actions';
|
2019-01-16 09:16:19 -06:00
|
|
|
import Page from 'app/core/components/Page/Page';
|
2019-03-19 12:24:09 -05:00
|
|
|
import { SlideDown } from 'app/core/components/Animations/SlideDown';
|
2018-09-27 04:26:47 -05:00
|
|
|
import ApiKeysAddedModal from './ApiKeysAddedModal';
|
|
|
|
import config from 'app/core/config';
|
|
|
|
import appEvents from 'app/core/app_events';
|
2018-10-10 18:07:54 -05:00
|
|
|
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
2020-04-21 04:42:21 -05:00
|
|
|
import {
|
|
|
|
DeleteButton,
|
|
|
|
EventsWithValidation,
|
|
|
|
InlineFormLabel,
|
|
|
|
LegacyForms,
|
|
|
|
ValidationEvents,
|
|
|
|
IconButton,
|
|
|
|
} from '@grafana/ui';
|
2020-04-07 01:50:54 -05:00
|
|
|
const { Input, Switch } = LegacyForms;
|
2020-09-03 01:54:06 -05:00
|
|
|
import { NavModel, dateTimeFormat, rangeUtil } from '@grafana/data';
|
2019-02-12 09:22:57 -06:00
|
|
|
import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
|
2019-06-26 01:47:03 -05:00
|
|
|
import { store } from 'app/store/store';
|
|
|
|
import { getTimeZone } from 'app/features/profile/state/selectors';
|
2020-01-13 01:03:22 -06:00
|
|
|
import { setSearchQuery } from './state/reducers';
|
2019-06-26 01:47:03 -05:00
|
|
|
|
|
|
|
const timeRangeValidationEvents: ValidationEvents = {
|
|
|
|
[EventsWithValidation.onBlur]: [
|
|
|
|
{
|
|
|
|
rule: value => {
|
|
|
|
if (!value) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
try {
|
2020-09-03 01:54:06 -05:00
|
|
|
rangeUtil.intervalToSeconds(value);
|
2019-06-26 01:47:03 -05:00
|
|
|
return true;
|
|
|
|
} catch {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
errorMessage: 'Not a valid duration',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
2018-09-25 09:23:43 -05:00
|
|
|
|
|
|
|
export interface Props {
|
|
|
|
navModel: NavModel;
|
|
|
|
apiKeys: ApiKey[];
|
|
|
|
searchQuery: string;
|
2018-10-11 04:49:34 -05:00
|
|
|
hasFetched: boolean;
|
2018-09-25 09:23:43 -05:00
|
|
|
loadApiKeys: typeof loadApiKeys;
|
|
|
|
deleteApiKey: typeof deleteApiKey;
|
2018-09-26 06:45:04 -05:00
|
|
|
setSearchQuery: typeof setSearchQuery;
|
|
|
|
addApiKey: typeof addApiKey;
|
2018-10-10 18:07:54 -05:00
|
|
|
apiKeysCount: number;
|
2019-11-20 05:14:57 -06:00
|
|
|
includeExpired: boolean;
|
2018-09-25 09:23:43 -05:00
|
|
|
}
|
|
|
|
|
2018-09-26 06:45:04 -05:00
|
|
|
export interface State {
|
|
|
|
isAdding: boolean;
|
|
|
|
newApiKey: NewApiKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum ApiKeyStateProps {
|
|
|
|
Name = 'name',
|
|
|
|
Role = 'role',
|
2019-06-26 01:47:03 -05:00
|
|
|
SecondsToLive = 'secondsToLive',
|
2018-09-26 06:45:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
const initialApiKeyState = {
|
|
|
|
name: '',
|
|
|
|
role: OrgRole.Viewer,
|
2019-06-26 01:47:03 -05:00
|
|
|
secondsToLive: '',
|
2018-09-26 06:45:04 -05:00
|
|
|
};
|
|
|
|
|
2019-06-26 01:47:03 -05:00
|
|
|
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';
|
|
|
|
|
2018-09-25 09:23:43 -05:00
|
|
|
export class ApiKeysPage extends PureComponent<Props, any> {
|
2019-08-01 07:38:34 -05:00
|
|
|
constructor(props: Props) {
|
2018-09-26 06:45:04 -05:00
|
|
|
super(props);
|
2019-11-20 05:14:57 -06:00
|
|
|
this.state = { isAdding: false, newApiKey: initialApiKeyState, includeExpired: false };
|
2018-09-26 06:45:04 -05:00
|
|
|
}
|
|
|
|
|
2018-09-25 09:23:43 -05:00
|
|
|
componentDidMount() {
|
|
|
|
this.fetchApiKeys();
|
|
|
|
}
|
|
|
|
|
|
|
|
async fetchApiKeys() {
|
2019-11-20 05:14:57 -06:00
|
|
|
await this.props.loadApiKeys(this.state.includeExpired);
|
2018-09-25 09:23:43 -05:00
|
|
|
}
|
|
|
|
|
2018-09-26 07:58:27 -05:00
|
|
|
onDeleteApiKey(key: ApiKey) {
|
2019-11-20 05:14:57 -06:00
|
|
|
this.props.deleteApiKey(key.id, this.props.includeExpired);
|
2018-09-25 09:23:43 -05:00
|
|
|
}
|
|
|
|
|
2019-02-12 01:03:43 -06:00
|
|
|
onSearchQueryChange = (value: string) => {
|
|
|
|
this.props.setSearchQuery(value);
|
2018-09-26 06:45:04 -05:00
|
|
|
};
|
|
|
|
|
2019-11-20 05:14:57 -06:00
|
|
|
onIncludeExpiredChange = (value: boolean) => {
|
|
|
|
this.setState({ hasFetched: false, includeExpired: value }, this.fetchApiKeys);
|
|
|
|
};
|
|
|
|
|
2018-09-26 06:45:04 -05:00
|
|
|
onToggleAdding = () => {
|
|
|
|
this.setState({ isAdding: !this.state.isAdding });
|
|
|
|
};
|
|
|
|
|
2019-08-01 07:38:34 -05:00
|
|
|
onAddApiKey = async (evt: any) => {
|
2018-09-26 06:45:04 -05:00
|
|
|
evt.preventDefault();
|
2018-09-27 04:26:47 -05:00
|
|
|
|
|
|
|
const openModal = (apiKey: string) => {
|
|
|
|
const rootPath = window.location.origin + config.appSubUrl;
|
|
|
|
const modalTemplate = ReactDOMServer.renderToString(<ApiKeysAddedModal apiKey={apiKey} rootPath={rootPath} />);
|
|
|
|
|
2019-10-14 03:27:47 -05:00
|
|
|
appEvents.emit(CoreEvents.showModal, {
|
2018-09-27 04:26:47 -05:00
|
|
|
templateHtml: modalTemplate,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-06-26 01:47:03 -05:00
|
|
|
// make sure that secondsToLive is number or null
|
|
|
|
const secondsToLive = this.state.newApiKey['secondsToLive'];
|
2020-09-03 01:54:06 -05:00
|
|
|
this.state.newApiKey['secondsToLive'] = secondsToLive ? rangeUtil.intervalToSeconds(secondsToLive) : null;
|
2019-11-20 05:14:57 -06:00
|
|
|
this.props.addApiKey(this.state.newApiKey, openModal, this.props.includeExpired);
|
2018-09-26 06:45:04 -05:00
|
|
|
this.setState((prevState: State) => {
|
|
|
|
return {
|
|
|
|
...prevState,
|
|
|
|
newApiKey: initialApiKeyState,
|
2018-10-31 06:50:44 -05:00
|
|
|
isAdding: false,
|
2018-09-26 06:45:04 -05:00
|
|
|
};
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-08-01 07:38:34 -05:00
|
|
|
onApiKeyStateUpdate = (evt: any, prop: string) => {
|
2018-09-26 06:45:04 -05:00
|
|
|
const value = evt.currentTarget.value;
|
|
|
|
this.setState((prevState: State) => {
|
2019-08-01 07:38:34 -05:00
|
|
|
const newApiKey: any = {
|
2018-09-26 06:45:04 -05:00
|
|
|
...prevState.newApiKey,
|
|
|
|
};
|
|
|
|
newApiKey[prop] = value;
|
|
|
|
|
|
|
|
return {
|
|
|
|
...prevState,
|
|
|
|
newApiKey: newApiKey,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2018-10-10 18:07:54 -05:00
|
|
|
renderEmptyList() {
|
2018-10-11 04:56:32 -05:00
|
|
|
const { isAdding } = this.state;
|
2018-10-10 18:07:54 -05:00
|
|
|
return (
|
2019-02-11 08:13:20 -06:00
|
|
|
<>
|
2018-10-11 04:56:32 -05:00
|
|
|
{!isAdding && (
|
|
|
|
<EmptyListCTA
|
2019-08-20 10:19:21 -05:00
|
|
|
title="You haven't added any API Keys yet."
|
@grafana/ui: Create Icon component and replace part of the icons (#23402)
* Part1: Unicons implementation (#23197)
* Create a new Icon component
* Update icons in main sidebar
* Update icons in Useful links and in react components on main site
* Update icons in Useful links and in main top navigation
* Adjust sizing
* Update panel navigation and timepicker
* Update icons in Panel menu
* NewPanelEditor: Fixed so that test alert rule works in new edit mode (#23179)
* Update icons in add panel widget
* Resolve merge conflict
* Fix part of the test errors and type errors
* Fix storybook errors
* Update getAvailableIcons import in storybook knobs
* Fix import path
* Fix SyntaxError: Cannot use import statement outside a module in test environment error
* Remove dynamic imports
* Remove types as using @ts-ignore
* Update snapshot test
* Add @iconscout/react-unicons to the shouldExclude list as it is blundled with es2015 syntax
* Remove color prop from icon, remove color implemetation in mono icons
* Update navbar styling
* Move toPascalCase to utils/string
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
* Resolve type errors resulted from merge
* Part2: Unicons implementation (#23266)
* Create a new Icon component
* Update icons in main sidebar
* Update icons in Useful links and in react components on main site
* Update icons in Useful links and in main top navigation
* Adjust sizing
* Update panel navigation and timepicker
* Update icons in Panel menu
* Update icons in add panel widget
* Resolve merge conflict
* Fix part of the test errors and type errors
* Fix storybook errors
* Update getAvailableIcons import in storybook knobs
* Fix import path
* Fix SyntaxError: Cannot use import statement outside a module in test environment error
* Remove dynamic imports
* Remove types as using @ts-ignore
* Update snapshot test
* Add @iconscout/react-unicons to the shouldExclude list as it is blundled with es2015 syntax
* Implment icons in Tabs
* Implement icons in search items and empty list
* Update buttons
* Update button-related snapshot tests
* Update icons in modals and page headers
* Create anfular wrapper and update all icons on search screen
* Update sizing, remove colors, update snapshot tests
* Remove color prop from icon, remove color implemetation in mono icons
* Remove color props from monochrome icons
* Complete update of icons for search screen
* Update icons for infor tooltips, playlist, permissions
* Support temporarly font awesome icons used in enterprise grafana
* Part1: Unicons implementation (#23197)
* Create a new Icon component
* Update icons in main sidebar
* Update icons in Useful links and in react components on main site
* Update icons in Useful links and in main top navigation
* Adjust sizing
* Update panel navigation and timepicker
* Update icons in Panel menu
* NewPanelEditor: Fixed so that test alert rule works in new edit mode (#23179)
* Update icons in add panel widget
* Resolve merge conflict
* Fix part of the test errors and type errors
* Fix storybook errors
* Update getAvailableIcons import in storybook knobs
* Fix import path
* Fix SyntaxError: Cannot use import statement outside a module in test environment error
* Remove dynamic imports
* Remove types as using @ts-ignore
* Update snapshot test
* Add @iconscout/react-unicons to the shouldExclude list as it is blundled with es2015 syntax
* Remove color prop from icon, remove color implemetation in mono icons
* Update navbar styling
* Move toPascalCase to utils/string
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
* Icons update
* Add optional chaining to for isFontAwesome variable
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
* Part3: Unicons implementation (#23356)
* Create a new Icon component
* Update icons in main sidebar
* Update icons in Useful links and in react components on main site
* Update icons in Useful links and in main top navigation
* Adjust sizing
* Update panel navigation and timepicker
* Update icons in Panel menu
* Update icons in add panel widget
* Resolve merge conflict
* Fix part of the test errors and type errors
* Fix storybook errors
* Update getAvailableIcons import in storybook knobs
* Fix import path
* Fix SyntaxError: Cannot use import statement outside a module in test environment error
* Remove dynamic imports
* Remove types as using @ts-ignore
* Update snapshot test
* Add @iconscout/react-unicons to the shouldExclude list as it is blundled with es2015 syntax
* Implment icons in Tabs
* Implement icons in search items and empty list
* Update buttons
* Update button-related snapshot tests
* Update icons in modals and page headers
* Create anfular wrapper and update all icons on search screen
* Update sizing, remove colors, update snapshot tests
* Remove color prop from icon, remove color implemetation in mono icons
* Remove color props from monochrome icons
* Complete update of icons for search screen
* Update icons for infor tooltips, playlist, permissions
* Support temporarly font awesome icons used in enterprise grafana
* Part1: Unicons implementation (#23197)
* Create a new Icon component
* Update icons in main sidebar
* Update icons in Useful links and in react components on main site
* Update icons in Useful links and in main top navigation
* Adjust sizing
* Update panel navigation and timepicker
* Update icons in Panel menu
* NewPanelEditor: Fixed so that test alert rule works in new edit mode (#23179)
* Update icons in add panel widget
* Resolve merge conflict
* Fix part of the test errors and type errors
* Fix storybook errors
* Update getAvailableIcons import in storybook knobs
* Fix import path
* Fix SyntaxError: Cannot use import statement outside a module in test environment error
* Remove dynamic imports
* Remove types as using @ts-ignore
* Update snapshot test
* Add @iconscout/react-unicons to the shouldExclude list as it is blundled with es2015 syntax
* Remove color prop from icon, remove color implemetation in mono icons
* Update navbar styling
* Move toPascalCase to utils/string
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
* Update icons in Explore
* Update icons in alerting
* Update + and x buttons
* Update icons in configurations and settings
* Update close icons
* Update icons in rich history
* Update alert messages
* Add optional chaining to for isFontAwesome variable
* Remove icon mock, set up jest.config
* Fix navbar plus icon
* Fir enable-bacground to enableBackgournd
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
* Merge remote branch origin master to icons-unicons
* Revert "Merge remote branch origin master to icons-unicons"
This reverts commit 3f25d50a39a940883fefe73ce51219139c1ed37f.
* Size-up dashnav icons
* Fix alerting icons, panel headers, update tests
* Fix typecheck error
* Adjustments - add panel icon, spacing
* Set TerserPlugin sourceMap to false to prevent running out of memory when publishing storybook
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2020-04-08 07:33:31 -05:00
|
|
|
buttonIcon="key-skeleton-alt"
|
2019-08-20 10:19:21 -05:00
|
|
|
buttonLink="#"
|
|
|
|
onClick={this.onToggleAdding}
|
|
|
|
buttonTitle=" New API Key"
|
|
|
|
proTip="Remember you can provide view-only API access to other applications."
|
2018-10-11 04:56:32 -05:00
|
|
|
/>
|
|
|
|
)}
|
2018-10-10 19:18:43 -05:00
|
|
|
{this.renderAddApiKeyForm()}
|
2019-02-11 08:13:20 -06:00
|
|
|
</>
|
2018-10-10 18:07:54 -05:00
|
|
|
);
|
2018-10-11 04:49:34 -05:00
|
|
|
}
|
|
|
|
|
2019-08-01 07:38:34 -05:00
|
|
|
formatDate(date: any, format?: string) {
|
2019-06-26 01:47:03 -05:00
|
|
|
if (!date) {
|
|
|
|
return 'No expiration date';
|
|
|
|
}
|
2020-04-27 08:28:06 -05:00
|
|
|
const timeZone = getTimeZone(store.getState().user);
|
|
|
|
return dateTimeFormat(date, { format, timeZone });
|
2019-06-26 01:47:03 -05:00
|
|
|
}
|
|
|
|
|
2018-10-10 19:18:43 -05:00
|
|
|
renderAddApiKeyForm() {
|
2018-09-26 06:45:04 -05:00
|
|
|
const { newApiKey, isAdding } = this.state;
|
2018-10-10 19:18:43 -05:00
|
|
|
|
|
|
|
return (
|
2018-10-31 06:50:44 -05:00
|
|
|
<SlideDown in={isAdding}>
|
2018-10-10 19:18:43 -05:00
|
|
|
<div className="cta-form">
|
2020-04-21 03:55:48 -05:00
|
|
|
<IconButton name="times" className="cta-form__close btn btn-transparent" onClick={this.onToggleAdding} />
|
2018-10-10 19:18:43 -05:00
|
|
|
<h5>Add API Key</h5>
|
|
|
|
<form className="gf-form-group" onSubmit={this.onAddApiKey}>
|
|
|
|
<div className="gf-form-inline">
|
|
|
|
<div className="gf-form max-width-21">
|
|
|
|
<span className="gf-form-label">Key name</span>
|
2019-03-25 09:53:05 -05:00
|
|
|
<Input
|
2018-10-10 19:18:43 -05:00
|
|
|
type="text"
|
|
|
|
className="gf-form-input"
|
|
|
|
value={newApiKey.name}
|
|
|
|
placeholder="Name"
|
|
|
|
onChange={evt => this.onApiKeyStateUpdate(evt, ApiKeyStateProps.Name)}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className="gf-form">
|
|
|
|
<span className="gf-form-label">Role</span>
|
|
|
|
<span className="gf-form-select-wrapper">
|
|
|
|
<select
|
|
|
|
className="gf-form-input gf-size-auto"
|
|
|
|
value={newApiKey.role}
|
|
|
|
onChange={evt => this.onApiKeyStateUpdate(evt, ApiKeyStateProps.Role)}
|
|
|
|
>
|
|
|
|
{Object.keys(OrgRole).map(role => {
|
|
|
|
return (
|
|
|
|
<option key={role} label={role} value={role}>
|
|
|
|
{role}
|
|
|
|
</option>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</select>
|
|
|
|
</span>
|
|
|
|
</div>
|
2019-06-26 01:47:03 -05:00
|
|
|
<div className="gf-form max-width-21">
|
2020-04-21 04:42:21 -05:00
|
|
|
<InlineFormLabel tooltip={tooltipText}>Time to live</InlineFormLabel>
|
2019-06-26 01:47:03 -05:00
|
|
|
<Input
|
|
|
|
type="text"
|
|
|
|
className="gf-form-input"
|
|
|
|
placeholder="1d"
|
|
|
|
validationEvents={timeRangeValidationEvents}
|
|
|
|
value={newApiKey.secondsToLive}
|
|
|
|
onChange={evt => this.onApiKeyStateUpdate(evt, ApiKeyStateProps.SecondsToLive)}
|
|
|
|
/>
|
|
|
|
</div>
|
2018-10-10 19:18:43 -05:00
|
|
|
<div className="gf-form">
|
2019-02-05 05:05:02 -06:00
|
|
|
<button className="btn gf-form-btn btn-primary">Add</button>
|
2018-10-10 19:18:43 -05:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
</SlideDown>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderApiKeyList() {
|
|
|
|
const { isAdding } = this.state;
|
2019-11-20 05:14:57 -06:00
|
|
|
const { apiKeys, searchQuery, includeExpired } = this.props;
|
2018-09-25 09:23:43 -05:00
|
|
|
|
|
|
|
return (
|
2019-02-11 08:13:20 -06:00
|
|
|
<>
|
2018-10-10 18:07:54 -05:00
|
|
|
<div className="page-action-bar">
|
|
|
|
<div className="gf-form gf-form--grow">
|
2019-02-12 09:22:57 -06:00
|
|
|
<FilterInput
|
|
|
|
labelClassName="gf-form--has-input-icon gf-form--grow"
|
|
|
|
inputClassName="gf-form-input"
|
|
|
|
placeholder="Search keys"
|
|
|
|
value={searchQuery}
|
|
|
|
onChange={this.onSearchQueryChange}
|
|
|
|
/>
|
2018-09-26 06:45:04 -05:00
|
|
|
</div>
|
|
|
|
|
2018-10-10 18:07:54 -05:00
|
|
|
<div className="page-action-bar__spacer" />
|
2019-02-05 05:05:02 -06:00
|
|
|
<button className="btn btn-primary pull-right" onClick={this.onToggleAdding} disabled={isAdding}>
|
2019-02-12 10:08:40 -06:00
|
|
|
Add API key
|
2018-10-10 18:07:54 -05:00
|
|
|
</button>
|
2018-09-25 09:23:43 -05:00
|
|
|
</div>
|
2018-10-10 18:07:54 -05:00
|
|
|
|
2018-10-10 19:18:43 -05:00
|
|
|
{this.renderAddApiKeyForm()}
|
2018-10-10 18:07:54 -05:00
|
|
|
|
|
|
|
<h3 className="page-heading">Existing Keys</h3>
|
2019-11-20 05:14:57 -06:00
|
|
|
<Switch
|
|
|
|
label="Show expired"
|
|
|
|
checked={includeExpired}
|
|
|
|
onChange={event => {
|
|
|
|
// @ts-ignore
|
|
|
|
this.onIncludeExpiredChange(event.target.checked);
|
|
|
|
}}
|
|
|
|
/>
|
2018-10-10 18:07:54 -05:00
|
|
|
<table className="filter-table">
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th>Name</th>
|
|
|
|
<th>Role</th>
|
2019-06-26 01:47:03 -05:00
|
|
|
<th>Expires</th>
|
2018-10-10 18:07:54 -05:00
|
|
|
<th style={{ width: '34px' }} />
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
{apiKeys.length > 0 ? (
|
|
|
|
<tbody>
|
|
|
|
{apiKeys.map(key => {
|
|
|
|
return (
|
|
|
|
<tr key={key.id}>
|
|
|
|
<td>{key.name}</td>
|
|
|
|
<td>{key.role}</td>
|
2019-06-26 01:47:03 -05:00
|
|
|
<td>{this.formatDate(key.expiration)}</td>
|
2018-10-10 18:07:54 -05:00
|
|
|
<td>
|
2019-12-13 05:42:18 -06:00
|
|
|
<DeleteButton size="sm" onConfirm={() => this.onDeleteApiKey(key)} />
|
2018-10-10 18:07:54 -05:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</tbody>
|
|
|
|
) : null}
|
|
|
|
</table>
|
2019-02-11 08:13:20 -06:00
|
|
|
</>
|
2018-10-10 18:07:54 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const { hasFetched, navModel, apiKeysCount } = this.props;
|
|
|
|
|
|
|
|
return (
|
2019-01-17 02:01:17 -06:00
|
|
|
<Page navModel={navModel}>
|
2019-01-16 09:16:19 -06:00
|
|
|
<Page.Contents isLoading={!hasFetched}>
|
2019-02-12 01:03:43 -06:00
|
|
|
{hasFetched && (apiKeysCount > 0 ? this.renderApiKeyList() : this.renderEmptyList())}
|
2019-01-16 09:16:19 -06:00
|
|
|
</Page.Contents>
|
|
|
|
</Page>
|
2018-09-25 09:23:43 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-01 07:38:34 -05:00
|
|
|
function mapStateToProps(state: any) {
|
2018-09-25 09:23:43 -05:00
|
|
|
return {
|
|
|
|
navModel: getNavModel(state.navIndex, 'apikeys'),
|
2018-09-26 06:45:04 -05:00
|
|
|
apiKeys: getApiKeys(state.apiKeys),
|
|
|
|
searchQuery: state.apiKeys.searchQuery,
|
2019-11-20 05:14:57 -06:00
|
|
|
includeExpired: state.includeExpired,
|
2018-10-10 18:07:54 -05:00
|
|
|
apiKeysCount: getApiKeysCount(state.apiKeys),
|
2018-10-31 06:50:44 -05:00
|
|
|
hasFetched: state.apiKeys.hasFetched,
|
2018-09-25 09:23:43 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const mapDispatchToProps = {
|
|
|
|
loadApiKeys,
|
|
|
|
deleteApiKey,
|
2018-09-26 06:45:04 -05:00
|
|
|
setSearchQuery,
|
|
|
|
addApiKey,
|
2018-09-25 09:23:43 -05:00
|
|
|
};
|
|
|
|
|
2019-11-19 07:59:39 -06:00
|
|
|
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(ApiKeysPage));
|