Skip to content
This repository has been archived by the owner on Sep 12, 2022. It is now read-only.

WIP: Minimum Deploy Option #781

Closed
wants to merge 14 commits into from
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
### Changed
- Fix format script and format codebase ([#782](https://github.com/cyverse/troposphere/pull/782))
- Travis will also check that the code is formatted from now on

### Added
- Add ability to create, edit, and delete "Personal Access Tokens" from the advanced section on the "settings" view ([#789](https://github.com/cyverse/troposphere/pull/789))
## [v33-0](https://github.com/cyverse/troposphere/compare/v32-0...v33-0) - 2018-08-06
### Changed
- Suggest adopting a changelog format
Expand Down Expand Up @@ -64,7 +65,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
([#750](https://github.com/cyverse/troposphere/pull/750))
- Solves problem where requests were being 'approved' while the resources
were not being updated

### Changed
- Change ./manage.py maintenance to be non-interactive
([#769](https://github.com/cyverse/troposphere/pull/769))
Expand Down
65 changes: 65 additions & 0 deletions troposphere/static/js/actions/APITokenActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import APITokenConstants from "constants/APITokenConstants";
import APIToken from "models/APIToken";
import NotificationController from "controllers/NotificationController";
import Utils from "./Utils";

export default {
create: name => {
if (!name) throw new Error("Missing Token name");
let apiToken = new APIToken({
name
});

let promise = Promise.resolve(apiToken.save());
promise.then(() => {
Utils.dispatch(APITokenConstants.ADD_TOKEN, {apiToken});
});
promise.catch(() => {
NotificationController.error(
"Error creating token.",
"Your login might be expired. If you continue to see this error " +
"after logging in again, contact support."
);
});
return promise;
},
update: (apiToken, newAttributes) => {
let prevAttributes = Object.assign({}, apiToken.attributes);

apiToken.set(newAttributes);
Utils.dispatch(APITokenConstants.UPDATE_TOKEN, {apiToken});
apiToken
.save(newAttributes, {patch: true})
.done(() => {
Utils.dispatch(APITokenConstants.UPDATE_TOKEN, {apiToken});
})
.fail(response => {
Utils.displayError({
title: "Token could not be saved",
response
});
apiToken.set(prevAttributes);
Utils.dispatch(APITokenConstants.UPDATE_TOKEN, {apiToken});
});
return apiToken;
},
destroy: apiToken => {
// Destroy token optimistically
Utils.dispatch(APITokenConstants.REMOVE_TOKEN, {apiToken});

apiToken
.destroy()
.done(() => {
Utils.dispatch(APITokenConstants.REMOVE_TOKEN, {apiToken});
})
.fail(() => {
Utils.dispatch(APITokenConstants.UPDATE_TOKEN, {apiToken});
NotificationController.error(
"Error deleting token.",
"Your login might be expired. If you continue to see this error " +
"after logging in again, contact support."
);
});
return apiToken;
}
};
1 change: 1 addition & 0 deletions troposphere/static/js/bootstrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ stores.AllocationSourceStore = require("stores/AllocationSourceStore");
import actions from "actions";

actions.AccountActions = require("actions/AccountActions");
actions.APITokenActions = require("actions/APITokenActions");
actions.BadgeActions = require("actions/BadgeActions");
actions.GroupActions = require("actions/GroupActions");
actions.ExternalLinkActions = require("actions/ExternalLinkActions");
Expand Down
11 changes: 11 additions & 0 deletions troposphere/static/js/collections/APITokenCollection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Backbone from "backbone";
import APIToken from "models/APIToken";
import globals from "globals";

export default Backbone.Collection.extend({
model: APIToken,
url: globals.API_V2_ROOT + "/access_tokens",
parse: function(data) {
return data.results;
}
});
182 changes: 182 additions & 0 deletions troposphere/static/js/components/modals/api_token/APITokenCreate.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import React from "react";
import BootstrapModalMixin from "components/mixins/BootstrapModalMixin";
import actions from "actions";
import {RaisedButton, CircularProgress} from "material-ui";
import WarningIcon from "material-ui/svg-icons/alert/warning";
import CopyButton from "components/common/ui/CopyButton";

export default React.createClass({
mixins: [BootstrapModalMixin],

getInitialState() {
return {
errorMsg: "",
name: "",
hash: "",
successView: false,
isSubmitting: false
};
},

updateName(e) {
this.setState({
name: e.target.value
});
},

onSubmit() {
const {name} = this.state;
this.setState({
isSubmitting: true
});
let promise = actions.APITokenActions.create(name.trim());
promise.then(this.onSuccess);
promise.catch(this.onError);
},

onSuccess(response) {
this.setState({
isSubmitting: false,
successView: true,
hash: response.token
});
},

onError(response) {
this.hide();
},

renderFormView() {
return (
<div>
<p>
Give your Access Token a name to help you remember where you
are using it. After you create your token you will only have
one chance to copy it somewhere safe.
</p>
<div className="form-group">
<label className="control-label">Name</label>
<div>
<input
type="text"
placeholder="My Token Name"
className="form-control"
onChange={this.updateName}
value={this.state.name}
/>
* Name Required
</div>
</div>
</div>
);
},

successStyles() {
return {
infoBlock: {display: "flex", marginBottom: "16px"},
infoBlockIcon: {marginRight: "16px", flex: "1 0 24px"},
figureCaption: {fontWeight: "600"},
figureBody: {
borderRadius: "4px",
border: "solid rgba(0,0,0,.3) 1px",
padding: "8px",
fontSize: "16px",
fontFamily: "monospace"
}
};
},

renderSuccessView() {
const {name, hash} = this.state;
const styles = this.successStyles();
return (
<div>
<h2
style={{marginBottom: "24px"}}
className="t-title">{`Token "${name}" Created Successfully!`}</h2>
<div style={styles.infoBlock}>
<WarningIcon style={styles.infoBlockIcon} />
<p>
Make sure you copy this token now. You will not have
another chance after this modal is closed!
</p>
</div>
<figure>
<figcaption style={styles.figureCaption}>
Your Public Access Token
</figcaption>
<div style={styles.figureBody}>
{hash}
<CopyButton text={hash} />
</div>
</figure>
</div>
);
},

renderSubmitButton() {
if (this.state.successView) {
return (
<RaisedButton
primary
onClick={this.hide}
label="Back to Settings"
/>
);
} else if (this.state.isSubmitting) {
return (
<RaisedButton
primary
disabled
icon={<CircularProgress size={24} color="rgba(0,0,0,.3)" />}
label="creating..."
/>
);
} else {
return (
<RaisedButton
primary
disabled={!this.state.name}
onClick={this.onSubmit}
label="Create Token"
/>
);
}
},

render() {
const {successView, isSubmitting} = this.state;

return (
<div className="modal fade">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
{this.renderCloseButton}
<h1 className="t-title">
Create Personal Access Token
</h1>
</div>
<div
style={{minHeight: "300px"}}
className="modal-body">
{successView
? this.renderSuccessView()
: this.renderFormView()}
</div>
<div className="modal-footer">
{successView || isSubmitting ? null : (
<RaisedButton
style={{marginRight: "16px"}}
onClick={this.hide}
label="Cancel"
/>
)}
{this.renderSubmitButton()}
</div>
</div>
</div>
</div>
);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from "react";
import Backbone from "backbone";
import actions from "actions";
import BootstrapModalMixin from "components/mixins/BootstrapModalMixin";
import {RaisedButton} from "material-ui";
import WarningIcon from "material-ui/svg-icons/alert/warning";
import subscribe from "utilities/subscribe";

const APITokenDelete = React.createClass({
propTypes: {
token: React.PropTypes.instanceOf(Backbone.Model)
},

mixins: [BootstrapModalMixin],

onSubmit() {
this.hide();
let token = this.props.token;
actions.APITokenActions.destroy(token);
},

render() {
const {token} = this.props;
const name = token.get("name");
return (
<div className="modal fade">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
{this.renderCloseButton()}
<h1 className="t-title">
Delete Personal Access Token
</h1>
</div>
<div
style={{minHeight: "300px"}}
className="modal-body">
<div style={{display: "flex"}}>
<WarningIcon
style={{
marginRight: "16px",
flex: "1 0 24px"
}}
/>{" "}
<p>
{`Are you sure you want to delete Access Token "${name}"? Any applications using this Token will not be able to connect to your account`}
</p>
</div>
</div>
<div className="modal-footer">
<RaisedButton
style={{marginRight: "16px"}}
onClick={this.hide}
label="Cancel"
/>
<RaisedButton
primary
onClick={this.onSubmit}
label="Yes, Delete Token"
/>
</div>
</div>
</div>
</div>
);
}
});

export default subscribe(APITokenDelete, ["APITokenStore"]);
Loading