|
@@ -1,10 +1,10 @@
|
|
|
-import React, { PureComponent } from 'react';
|
|
|
|
|
|
|
+import React, { PureComponent } from 'react';
|
|
|
import ReactDOMServer from 'react-dom/server';
|
|
import ReactDOMServer from 'react-dom/server';
|
|
|
import { connect } from 'react-redux';
|
|
import { connect } from 'react-redux';
|
|
|
import { hot } from 'react-hot-loader';
|
|
import { hot } from 'react-hot-loader';
|
|
|
import { NavModel, ApiKey, NewApiKey, OrgRole } from 'app/types';
|
|
import { NavModel, ApiKey, NewApiKey, OrgRole } from 'app/types';
|
|
|
import { getNavModel } from 'app/core/selectors/navModel';
|
|
import { getNavModel } from 'app/core/selectors/navModel';
|
|
|
-import { getApiKeys } from './state/selectors';
|
|
|
|
|
|
|
+import { getApiKeys, getApiKeysCount } from './state/selectors';
|
|
|
import { loadApiKeys, deleteApiKey, setSearchQuery, addApiKey } from './state/actions';
|
|
import { loadApiKeys, deleteApiKey, setSearchQuery, addApiKey } from './state/actions';
|
|
|
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
|
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
|
|
import SlideDown from 'app/core/components/Animations/SlideDown';
|
|
import SlideDown from 'app/core/components/Animations/SlideDown';
|
|
@@ -12,6 +12,7 @@ import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
|
|
import ApiKeysAddedModal from './ApiKeysAddedModal';
|
|
import ApiKeysAddedModal from './ApiKeysAddedModal';
|
|
|
import config from 'app/core/config';
|
|
import config from 'app/core/config';
|
|
|
import appEvents from 'app/core/app_events';
|
|
import appEvents from 'app/core/app_events';
|
|
|
|
|
+import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
|
|
|
|
|
|
|
export interface Props {
|
|
export interface Props {
|
|
|
navModel: NavModel;
|
|
navModel: NavModel;
|
|
@@ -22,6 +23,7 @@ export interface Props {
|
|
|
deleteApiKey: typeof deleteApiKey;
|
|
deleteApiKey: typeof deleteApiKey;
|
|
|
setSearchQuery: typeof setSearchQuery;
|
|
setSearchQuery: typeof setSearchQuery;
|
|
|
addApiKey: typeof addApiKey;
|
|
addApiKey: typeof addApiKey;
|
|
|
|
|
+ apiKeysCount: number;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export interface State {
|
|
export interface State {
|
|
@@ -101,115 +103,135 @@ export class ApiKeysPage extends PureComponent<Props, any> {
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- renderTable() {
|
|
|
|
|
- const { apiKeys } = this.props;
|
|
|
|
|
-
|
|
|
|
|
- return [
|
|
|
|
|
- <h3 key="header" className="page-heading">
|
|
|
|
|
- Existing Keys
|
|
|
|
|
- </h3>,
|
|
|
|
|
- <table key="table" className="filter-table">
|
|
|
|
|
- <thead>
|
|
|
|
|
- <tr>
|
|
|
|
|
- <th>Name</th>
|
|
|
|
|
- <th>Role</th>
|
|
|
|
|
- <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>
|
|
|
|
|
- <td>
|
|
|
|
|
- <a onClick={() => this.onDeleteApiKey(key)} className="btn btn-danger btn-mini">
|
|
|
|
|
- <i className="fa fa-remove" />
|
|
|
|
|
- </a>
|
|
|
|
|
- </td>
|
|
|
|
|
- </tr>
|
|
|
|
|
- );
|
|
|
|
|
- })}
|
|
|
|
|
- </tbody>
|
|
|
|
|
- )}
|
|
|
|
|
- </table>,
|
|
|
|
|
- ];
|
|
|
|
|
|
|
+ renderEmptyList() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="page-container page-body">
|
|
|
|
|
+ <EmptyListCTA
|
|
|
|
|
+ model={{
|
|
|
|
|
+ title: "You haven't added any API Keys yet.",
|
|
|
|
|
+ buttonIcon: 'fa fa-plus',
|
|
|
|
|
+ buttonLink: 'org/apikeys/new',
|
|
|
|
|
+ buttonTitle: ' New API Key',
|
|
|
|
|
+ proTip: 'Assign folder and dashboard permissions to teams instead of users to ease administration.',
|
|
|
|
|
+ proTipLink: '',
|
|
|
|
|
+ proTipLinkTitle: '',
|
|
|
|
|
+ proTipTarget: '_blank',
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- render() {
|
|
|
|
|
|
|
+ renderApiKeyList() {
|
|
|
const { newApiKey, isAdding } = this.state;
|
|
const { newApiKey, isAdding } = this.state;
|
|
|
- const { hasFetched, navModel, searchQuery } = this.props;
|
|
|
|
|
|
|
+ const { apiKeys, searchQuery } = this.props;
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
- <div>
|
|
|
|
|
- <PageHeader model={navModel} />
|
|
|
|
|
- <div className="page-container page-body">
|
|
|
|
|
- <div className="page-action-bar">
|
|
|
|
|
- <div className="gf-form gf-form--grow">
|
|
|
|
|
- <label className="gf-form--has-input-icon gf-form--grow">
|
|
|
|
|
- <input
|
|
|
|
|
- type="text"
|
|
|
|
|
- className="gf-form-input"
|
|
|
|
|
- placeholder="Search keys"
|
|
|
|
|
- value={searchQuery}
|
|
|
|
|
- onChange={this.onSearchQueryChange}
|
|
|
|
|
- />
|
|
|
|
|
- <i className="gf-form-input-icon fa fa-search" />
|
|
|
|
|
- </label>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div className="page-action-bar__spacer" />
|
|
|
|
|
- <button className="btn btn-success pull-right" onClick={this.onToggleAdding} disabled={isAdding}>
|
|
|
|
|
- <i className="fa fa-plus" /> Add API Key
|
|
|
|
|
- </button>
|
|
|
|
|
|
|
+ <div className="page-container page-body">
|
|
|
|
|
+ <div className="page-action-bar">
|
|
|
|
|
+ <div className="gf-form gf-form--grow">
|
|
|
|
|
+ <label className="gf-form--has-input-icon gf-form--grow">
|
|
|
|
|
+ <input
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ className="gf-form-input"
|
|
|
|
|
+ placeholder="Search keys"
|
|
|
|
|
+ value={searchQuery}
|
|
|
|
|
+ onChange={this.onSearchQueryChange}
|
|
|
|
|
+ />
|
|
|
|
|
+ <i className="gf-form-input-icon fa fa-search" />
|
|
|
|
|
+ </label>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <SlideDown in={isAdding}>
|
|
|
|
|
- <div className="cta-form">
|
|
|
|
|
- <button className="cta-form__close btn btn-transparent" onClick={this.onToggleAdding}>
|
|
|
|
|
- <i className="fa fa-close" />
|
|
|
|
|
- </button>
|
|
|
|
|
- <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>
|
|
|
|
|
- <input
|
|
|
|
|
- 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>
|
|
|
|
|
- <div className="gf-form">
|
|
|
|
|
- <button className="btn gf-form-btn btn-success">Add</button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </form>
|
|
|
|
|
- </div>
|
|
|
|
|
- </SlideDown>
|
|
|
|
|
- {hasFetched ? this.renderTable() : <PageLoader pageName="Api keys" />}
|
|
|
|
|
|
|
+ <div className="page-action-bar__spacer" />
|
|
|
|
|
+ <button className="btn btn-success pull-right" onClick={this.onToggleAdding} disabled={isAdding}>
|
|
|
|
|
+ <i className="fa fa-plus" /> Add API Key
|
|
|
|
|
+ </button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ <SlideDown in={isAdding}>
|
|
|
|
|
+ <div className="cta-form">
|
|
|
|
|
+ <button className="cta-form__close btn btn-transparent" onClick={this.onToggleAdding}>
|
|
|
|
|
+ <i className="fa fa-close" />
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <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>
|
|
|
|
|
+ <input
|
|
|
|
|
+ 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>
|
|
|
|
|
+ <div className="gf-form">
|
|
|
|
|
+ <button className="btn gf-form-btn btn-success">Add</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </SlideDown>
|
|
|
|
|
+
|
|
|
|
|
+ <h3 className="page-heading">Existing Keys</h3>
|
|
|
|
|
+ <table className="filter-table">
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>Name</th>
|
|
|
|
|
+ <th>Role</th>
|
|
|
|
|
+ <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>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <a onClick={() => this.onDeleteApiKey(key)} className="btn btn-danger btn-mini">
|
|
|
|
|
+ <i className="fa fa-remove" />
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ );
|
|
|
|
|
+ })}
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ ) : null}
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ render() {
|
|
|
|
|
+ const { hasFetched, navModel, apiKeysCount } = this.props;
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <PageHeader model={navModel} />
|
|
|
|
|
+ {hasFetched ?
|
|
|
|
|
+ (apiKeysCount > 0 ? this.renderApiKeyList() : this.renderEmptyList())
|
|
|
|
|
+ : <PageLoader pageName="Api keys" />}
|
|
|
</div>
|
|
</div>
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
@@ -220,7 +242,8 @@ function mapStateToProps(state) {
|
|
|
navModel: getNavModel(state.navIndex, 'apikeys'),
|
|
navModel: getNavModel(state.navIndex, 'apikeys'),
|
|
|
apiKeys: getApiKeys(state.apiKeys),
|
|
apiKeys: getApiKeys(state.apiKeys),
|
|
|
searchQuery: state.apiKeys.searchQuery,
|
|
searchQuery: state.apiKeys.searchQuery,
|
|
|
- hasFetched: state.apiKeys.hasFetched,
|
|
|
|
|
|
|
+ apiKeysCount: getApiKeysCount(state.apiKeys),
|
|
|
|
|
+ hasFetched: state.apiKeys.hasFetched
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|