Displays all API keys in a table, including new token information
This commit is contained in:
parent
69d5a87861
commit
504eacaf64
3 changed files with 106 additions and 50 deletions
|
@ -222,10 +222,10 @@ export function updateSettings(formValues) {
|
||||||
.catch(response => Promise.reject(new Error(response.data.error)));
|
.catch(response => Promise.reject(new Error(response.data.error)));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createApiKeySuccess(token) {
|
export function createApiKeySuccess(user) {
|
||||||
return {
|
return {
|
||||||
type: ActionTypes.API_KEY_CREATED,
|
type: ActionTypes.API_KEY_CREATED,
|
||||||
token
|
user
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,9 +233,7 @@ export function createApiKey(label) {
|
||||||
return dispatch =>
|
return dispatch =>
|
||||||
axios.post(`${ROOT_URL}/account/api-keys`, { label }, { withCredentials: true })
|
axios.post(`${ROOT_URL}/account/api-keys`, { label }, { withCredentials: true })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const { token } = response.data;
|
dispatch(createApiKeySuccess(response.data));
|
||||||
dispatch(createApiKeySuccess(token));
|
|
||||||
return token;
|
|
||||||
})
|
})
|
||||||
.catch(response => Promise.reject(new Error(response.data.error)));
|
.catch(response => Promise.reject(new Error(response.data.error)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,62 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import InlineSVG from 'react-inlinesvg';
|
||||||
|
import format from 'date-fns/format';
|
||||||
|
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
|
||||||
|
import orderBy from 'lodash/orderBy';
|
||||||
|
|
||||||
|
const trashCan = require('../../../images/trash-can.svg');
|
||||||
|
|
||||||
|
function NewTokenDisplay({ token }) {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<p>Make sure to copy your new personal access token now. You won’t be able to see it again!</p>
|
||||||
|
<p><input type="text" readOnly value={token} /></p>
|
||||||
|
<button>Copy</button>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TokenMetadataList({ tokens, onRemove }) {
|
||||||
|
return (
|
||||||
|
<table className="form__table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Created on</th>
|
||||||
|
<th>Last used</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{orderBy(tokens, ['createdAt'], ['desc']).map((v) => {
|
||||||
|
const keyRow = (
|
||||||
|
<tr key={v.id}>
|
||||||
|
<td>{v.label}</td>
|
||||||
|
<td>{format(new Date(v.createdAt), 'MMM D, YYYY h:mm A')}</td>
|
||||||
|
<td>{distanceInWordsToNow(new Date(v.lastUsedAt), { addSuffix: true })}</td>
|
||||||
|
<td>
|
||||||
|
<button className="account__tokens__delete-button" onClick={() => onRemove(v)}>
|
||||||
|
<InlineSVG src={trashCan} alt="Delete Key" />
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
|
||||||
|
const newKeyValue = v.token && (
|
||||||
|
<tr key={v.id}>
|
||||||
|
<td colSpan="4">
|
||||||
|
<NewTokenDisplay token={v.token} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
|
||||||
|
return [keyRow, newKeyValue];
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class APIKeyForm extends React.Component {
|
class APIKeyForm extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -7,62 +64,61 @@ class APIKeyForm extends React.Component {
|
||||||
this.state = { keyLabel: '' };
|
this.state = { keyLabel: '' };
|
||||||
|
|
||||||
this.addKey = this.addKey.bind(this);
|
this.addKey = this.addKey.bind(this);
|
||||||
|
this.removeKey = this.removeKey.bind(this);
|
||||||
|
this.renderApiKeys = this.renderApiKeys.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
addKey(event) {
|
addKey(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
document.getElementById('addKeyForm').reset();
|
const { keyLabel } = this.state;
|
||||||
this.props.createApiKey(this.state.keyLabel)
|
|
||||||
.then(newToken => this.setState({ newToken }));
|
this.setState({
|
||||||
this.state.keyLabel = '';
|
keyLabel: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.createApiKey(keyLabel);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeKey(keyId) {
|
removeKey(key) {
|
||||||
this.props.removeApiKey(keyId);
|
const message = `Are you sure you want to delete "${key.label}"?`;
|
||||||
|
|
||||||
|
if (window.confirm(message)) {
|
||||||
|
this.props.removeApiKey(key.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderApiKeys() {
|
||||||
|
const hasApiKeys = this.props.apiKeys && this.props.apiKeys.length > 0;
|
||||||
|
|
||||||
|
if (hasApiKeys) {
|
||||||
|
return (
|
||||||
|
<TokenMetadataList tokens={this.props.apiKeys} onRemove={this.removeKey} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <p>You have no API keys</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { newToken } = this.state;
|
return (
|
||||||
|
|
||||||
const content = newToken ?
|
|
||||||
(
|
|
||||||
<div>
|
<div>
|
||||||
<p>Here is your new key. Copy it somewhere, you won't be able to see it later !</p>
|
<form id="addKeyForm" className="form" onSubmit={this.addKey}>
|
||||||
<input type="text" readOnly value={newToken} />
|
<label htmlFor="keyLabel" className="form__label">Name</label>
|
||||||
<button>Copy to clipboard</button>
|
|
||||||
</div>) :
|
|
||||||
(<form id="addKeyForm" className="form" onSubmit={this.addKey}>
|
|
||||||
<h2 className="form__label">Key label</h2>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form__input"
|
className="form__input"
|
||||||
placeholder="A name you will be able to recognize"
|
placeholder="What is this token for?"
|
||||||
id="keyLabel"
|
id="keyLabel"
|
||||||
onChange={(event) => { this.setState({ keyLabel: event.target.value }); }}
|
onChange={(event) => { this.setState({ keyLabel: event.target.value }); }}
|
||||||
/><br />
|
/>
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Create new Key"
|
value="Create new Key"
|
||||||
disabled={this.state.keyLabel === ''}
|
disabled={this.state.keyLabel === ''}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
);
|
{this.renderApiKeys()}
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{content}
|
|
||||||
<table className="form__table">
|
|
||||||
<tbody id="form__table_new_key"></tbody>
|
|
||||||
<tbody>
|
|
||||||
{this.props.apiKeys && this.props.apiKeys.map(v => (
|
|
||||||
<tr key={v.id}>
|
|
||||||
<td><b>{v.label}</b><br />Created on: {v.createdAt}</td>
|
|
||||||
<td>Last used on:<br /> {v.lastUsedAt}</td>
|
|
||||||
<td><button className="form__table-button-remove" onClick={() => this.removeKey(v.id)}>Delete</button></td>
|
|
||||||
</tr>))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,9 @@ const apiKeySchema = new Schema({
|
||||||
}, { timestamps: true, _id: true });
|
}, { timestamps: true, _id: true });
|
||||||
|
|
||||||
apiKeySchema.virtual('publicFields').get(function publicFields() {
|
apiKeySchema.virtual('publicFields').get(function publicFields() {
|
||||||
return { id: this.id, label: this.label, lastUsedAt: this.lastUsedAt };
|
return {
|
||||||
|
id: this.id, label: this.label, lastUsedAt: this.lastUsedAt, createdAt: this.createdAt
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
apiKeySchema.virtual('id').get(function getApiKeyId() {
|
apiKeySchema.virtual('id').get(function getApiKeyId() {
|
||||||
|
|
Loading…
Reference in a new issue