From 0ee80d97c9910e4260a0a19a15bd3faa1f6e47bc Mon Sep 17 00:00:00 2001 From: tharon-c Date: Wed, 8 Aug 2018 23:28:00 +0000 Subject: [PATCH 01/14] Stub out API Token feature --- .../static/js/actions/APITokenActions.js | 47 +++++ .../js/collections/APITokenCollection.js | 11 ++ .../modals/api_token/APITokenCreate.jsx | 183 ++++++++++++++++++ .../modals/api_token/APITokenDelete.jsx | 60 ++++++ .../modals/api_token/APITokenEdit.jsx | 84 ++++++++ .../settings/AdvancedSettingsPage.jsx | 2 + .../settings/advanced/TokenListView.jsx | 141 ++++++++++++++ .../static/js/constants/APITokenConstants.js | 5 + troposphere/static/js/models/APIToken.js | 6 + troposphere/static/js/stores/APITokenStore.js | 40 ++++ troposphere/static/js/utilities/subscribe.js | 2 + 11 files changed, 581 insertions(+) create mode 100644 troposphere/static/js/actions/APITokenActions.js create mode 100644 troposphere/static/js/collections/APITokenCollection.js create mode 100644 troposphere/static/js/components/modals/api_token/APITokenCreate.jsx create mode 100644 troposphere/static/js/components/modals/api_token/APITokenDelete.jsx create mode 100644 troposphere/static/js/components/modals/api_token/APITokenEdit.jsx create mode 100644 troposphere/static/js/components/settings/advanced/TokenListView.jsx create mode 100644 troposphere/static/js/constants/APITokenConstants.js create mode 100644 troposphere/static/js/models/APIToken.js create mode 100644 troposphere/static/js/stores/APITokenStore.js diff --git a/troposphere/static/js/actions/APITokenActions.js b/troposphere/static/js/actions/APITokenActions.js new file mode 100644 index 000000000..854321c1a --- /dev/null +++ b/troposphere/static/js/actions/APITokenActions.js @@ -0,0 +1,47 @@ +import APITokenConstants from "constants/APITokenConstants"; +import APIToken from "models/APIToken"; +import Utils from "./Utils"; + +export default { + create: ({name, atmo_user}) => { + if (!name) throw new Error("Missing Token name"); + if (!atmo_user) throw new Error("Missing Token author"); + let apiToken = new APIToken({ + name, + atmo_user + }); + + // Add token optimistically + Utils.dispatch(APITokenConstants.ADD_TOKEN, {apiToken}); + + apiToken + .save() + .done(() => { + Utils.dispatch(APITokenConstants.UPDATE_TOKEN, {apiToken}); + }) + .fail(() => { + Utils.dispatch(APITokenConstants.REMOVE_TOKEN, {apiToken}); + }); + return apiToken; + }, + 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; + } +}; diff --git a/troposphere/static/js/collections/APITokenCollection.js b/troposphere/static/js/collections/APITokenCollection.js new file mode 100644 index 000000000..0cdf93ece --- /dev/null +++ b/troposphere/static/js/collections/APITokenCollection.js @@ -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 + "/ssh_keys", + parse: function(data) { + return [{name: "Home CLI", id: 2}]; + } +}); diff --git a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx new file mode 100644 index 000000000..2c6b85e9f --- /dev/null +++ b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx @@ -0,0 +1,183 @@ +import React from "react"; +import Backbone from "backbone"; +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"; +import Code from "components/common/ui/Code"; + +export default React.createClass({ + mixins: [BootstrapModalMixin], + propTypes: { + user: React.PropTypes.number.isRequired + }, + + getInitialState() { + return { + errorMsg: "", + name: this.props.name || "", + successView: false + }; + }, + + updateName(e) { + this.setState({ + name: e.target.value + }); + }, + + onSubmit() { + const {name} = this.state; + const {user} = this.props; + let attributes = { + name: name.trim(), + atmo_user: user + }; + this.setState({ + isSubmitting: true + }); + setTimeout(() => { + this.setState({ + isSubmitting: false, + successView: true, + hash: "343rw24fg983498j3urfu39" + }); + }, 2000); + }, + + renderFormView() { + return ( +
+

+ 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. +

+
+ +
+ + * Name Required +
+
+
+ ); + }, + + 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 ( +
+

{`Token "${name}" Created Successfully!`}

+
+ +

+ Make sure you copy this token now. You will not have + another chance after this modal is closed! +

+
+
+
+ Your Public Access Token +
+
+ {hash} + +
+
+
+ ); + }, + + renderSubmitButton() { + if (this.state.successView) { + return ( + + ); + } else if (this.state.isSubmitting) { + return ( + } + label="creating..." + /> + ); + } else { + return ( + + ); + } + }, + + render() { + const {successView, isSubmitting} = this.state; + const {edit} = this.props; + + return ( +
+
+
+
+ {this.renderCloseButton} +

+ Create Personal Access Token +

+
+
+ {successView + ? this.renderSuccessView() + : this.renderFormView()} +
+
+ {successView || isSubmitting ? null : ( + + )} + {this.renderSubmitButton()} +
+
+
+
+ ); + } +}); diff --git a/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx b/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx new file mode 100644 index 000000000..24a666933 --- /dev/null +++ b/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx @@ -0,0 +1,60 @@ +import React from "react"; +import Backbone from "backbone"; +import BootstrapModalMixin from "components/mixins/BootstrapModalMixin"; +import actions from "actions"; +import {RaisedButton} from "material-ui"; +import WarningIcon from "material-ui/svg-icons/alert/warning"; + +export default React.createClass({ + propTypes: { + token: React.PropTypes.instanceOf(Backbone.Model), + user: React.PropTypes.number.isRequired + }, + + mixins: [BootstrapModalMixin], + + onSubmit() { + this.hide(); + }, + + render() { + const {token} = this.props; + const name = token.get("name"); + return ( +
+
+
+
+ {this.renderCloseButton()} +

+ Delete Personal Access Token +

+
+
+
+ {" "} +

+ {`Are you sure you want to delete Access Token "${name}"? Any applications using this Token will not be able to connect to your account`} +

+
+
+
+ + +
+
+
+
+ ); + } +}); diff --git a/troposphere/static/js/components/modals/api_token/APITokenEdit.jsx b/troposphere/static/js/components/modals/api_token/APITokenEdit.jsx new file mode 100644 index 000000000..020fbb6fc --- /dev/null +++ b/troposphere/static/js/components/modals/api_token/APITokenEdit.jsx @@ -0,0 +1,84 @@ +import React from "react"; +import Backbone from "backbone"; +import BootstrapModalMixin from "components/mixins/BootstrapModalMixin"; +import actions from "actions"; +import {RaisedButton} from "material-ui"; + +export default React.createClass({ + mixins: [BootstrapModalMixin], + propTypes: { + token: React.PropTypes.number.isRequired + }, + + getInitialState() { + const {token} = this.props; + return { + errorMsg: "", + name: token.get("name") + }; + }, + + updateName(e) { + this.setState({ + name: e.target.value + }); + }, + + onSubmit() { + const {name} = this.state; + this.hide(); + }, + + render() { + return ( +
+
+
+
+ {this.renderCloseButton} +

+ Edit Personal Access Token Name +

+
+
+

+ You can change the name of your Access Token. + Note that the Token Hash can not be changed. If + you want to change the hash and keep the name, + delete this Token and create a new Token with + the same name. +

+
+ +
+ + * Name Required +
+
+
+
+ + +
+
+
+
+ ); + } +}); diff --git a/troposphere/static/js/components/settings/AdvancedSettingsPage.jsx b/troposphere/static/js/components/settings/AdvancedSettingsPage.jsx index 1688f09e0..d6aba98ca 100644 --- a/troposphere/static/js/components/settings/AdvancedSettingsPage.jsx +++ b/troposphere/static/js/components/settings/AdvancedSettingsPage.jsx @@ -1,6 +1,7 @@ import React from "react"; import featureFlags from "utilities/featureFlags"; import SSHConfiguration from "components/settings/advanced/SSHConfiguration"; +import TokenListView from "components/settings/advanced/TokenListView"; import ScriptListView from "components/settings/advanced/ScriptListView"; import ClientCredentials from "components/settings/advanced/ClientCredentials"; @@ -39,6 +40,7 @@ export default React.createClass({ {this.renderClientCredentials()} {this.renderScripts()} + ); diff --git a/troposphere/static/js/components/settings/advanced/TokenListView.jsx b/troposphere/static/js/components/settings/advanced/TokenListView.jsx new file mode 100644 index 000000000..ce7c346f3 --- /dev/null +++ b/troposphere/static/js/components/settings/advanced/TokenListView.jsx @@ -0,0 +1,141 @@ +import React from "react"; +import ModalHelpers from "components/modals/ModalHelpers"; +import APITokenCreate from "components/modals/api_token/APITokenCreate"; +import APITokenDelete from "components/modals/api_token/APITokenDelete"; +import APITokenEdit from "components/modals/api_token/APITokenEdit"; +import subscribe from "utilities/subscribe"; + +import globals from "globals"; + +const APITokenConfiguration = React.createClass({ + getInitialState() { + let {ProfileStore} = this.props.subscriptions; + var profile = ProfileStore.get(); + return { + profile + }; + }, + + updateState() { + this.setState(this.getInitialState()); + }, + launchDeleteModal(token) { + ModalHelpers.renderModal( + APITokenDelete, + { + token + }, + () => {} + ); + }, + launchCreateModal(user) { + ModalHelpers.renderModal( + APITokenCreate, + { + user + }, + () => {} + ); + }, + launchEditModal(token) { + ModalHelpers.renderModal( + APITokenEdit, + { + token, + edit: true + }, + () => {} + ); + }, + + style() { + return { + td: { + wordWrap: "break-word", + whiteSpace: "normal" + }, + labelName: { + width: "100%" + } + }; + }, + + renderTokenRow(apiToken) { + let {td} = this.style(); + + // Set a key that lexicograhically sorts first by title then by cid. + // Cannot sort by id, because recently created bootscript has no id + let key = apiToken.get("name") + apiToken.cid; + return ( + + {apiToken.get("name")} + + + + {" "} + + + + + + ); + }, + + render() { + let {APITokenStore} = this.props.subscriptions, + profile = this.state.profile, + api_token = APITokenStore.getAll(); + debugger; + return ( +
+

Personal Access Tokens

+
+

+ Personal Access Tokens are API Tokens that can be used + instead of passwords for authentication. Other + aplications can use Personal Access Tokens to access + Atmosphere services under your account. +

+
+
+ + + + + + + + + {api_token + ? api_token.map(this.renderTokenRow) + : []} + + + + +
NameActions
+ + + + + +
+
+
+ ); + } +}); + +export default subscribe(APITokenConfiguration, [ + "APITokenStore", + "ProfileStore" +]); diff --git a/troposphere/static/js/constants/APITokenConstants.js b/troposphere/static/js/constants/APITokenConstants.js new file mode 100644 index 000000000..fb094bcab --- /dev/null +++ b/troposphere/static/js/constants/APITokenConstants.js @@ -0,0 +1,5 @@ +export default { + ADD_TOKEN: "ADD_TOKEN", + UPDATE_TOKEN: "UPDATE_TOKEN", + REMOVE_TOKEN: "REMOVE_TOKEN" +}; diff --git a/troposphere/static/js/models/APIToken.js b/troposphere/static/js/models/APIToken.js new file mode 100644 index 000000000..0a737decf --- /dev/null +++ b/troposphere/static/js/models/APIToken.js @@ -0,0 +1,6 @@ +import Backbone from "backbone"; +import globals from "globals"; + +export default Backbone.Model.extend({ + urlRoot: globals.API_V2_ROOT + "/ssh_keys" +}); diff --git a/troposphere/static/js/stores/APITokenStore.js b/troposphere/static/js/stores/APITokenStore.js new file mode 100644 index 000000000..cce1796cc --- /dev/null +++ b/troposphere/static/js/stores/APITokenStore.js @@ -0,0 +1,40 @@ +import BaseStore from "stores/BaseStore"; +import APITokenConstants from "constants/APITokenConstants"; +import APITokenCollection from "collections/APITokenCollection"; +import Dispatcher from "dispatchers/Dispatcher"; + +var APITokenStore = BaseStore.extend({ + collection: APITokenCollection +}); + +let store = new APITokenStore(); +Dispatcher.register(function(dispatch) { + var actionType = dispatch.action.actionType; + var payload = dispatch.action.payload; + var options = dispatch.action.options || options; + + switch (actionType) { + case APITokenConstants.ADD_TOKEN: + store.add(payload.sshKey); + break; + + case APITokenConstants.REMOVE_TOKEN: + store.remove(payload.sshKey); + break; + + case APITokenConstants.UPDATE_TOKEN: + store.update(payload.sshKey); + break; + + default: + return true; + } + + if (!options.silent) { + store.emitChange(); + } + + return true; +}); + +export default store; diff --git a/troposphere/static/js/utilities/subscribe.js b/troposphere/static/js/utilities/subscribe.js index d4aac2c3a..4b726f250 100644 --- a/troposphere/static/js/utilities/subscribe.js +++ b/troposphere/static/js/utilities/subscribe.js @@ -38,6 +38,7 @@ import AdminResourceRequestStore from "stores/AdminResourceRequestStore"; import IdentityMembershipStore from "stores/IdentityMembershipStore"; import StatusStore from "stores/StatusStore"; import SSHKeyStore from "stores/SSHKeyStore"; +import APITokenStore from "stores/APITokenStore"; import QuotaStore from "stores/QuotaStore"; import SizeStore from "stores/SizeStore"; import TagStore from "stores/TagStore"; @@ -90,6 +91,7 @@ let stores = { IdentityMembershipStore, StatusStore, SSHKeyStore, + APITokenStore, QuotaStore, SizeStore, TagStore, From 722cec8272be1220c5ceb857da1fc68bedc78d7d Mon Sep 17 00:00:00 2001 From: Calvin Mclean Date: Thu, 9 Aug 2018 14:17:44 -0700 Subject: [PATCH 02/14] Enable APIToken viewing and creating --- troposphere/static/js/bootstrapper.js | 1 + troposphere/static/js/collections/APITokenCollection.js | 4 ++-- .../js/components/modals/api_token/APITokenCreate.jsx | 3 ++- .../js/components/settings/advanced/TokenListView.jsx | 1 - troposphere/static/js/models/APIToken.js | 2 +- troposphere/static/js/stores/APITokenStore.js | 6 +++--- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/troposphere/static/js/bootstrapper.js b/troposphere/static/js/bootstrapper.js index 49d2a5c31..95b61d169 100644 --- a/troposphere/static/js/bootstrapper.js +++ b/troposphere/static/js/bootstrapper.js @@ -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"); diff --git a/troposphere/static/js/collections/APITokenCollection.js b/troposphere/static/js/collections/APITokenCollection.js index 0cdf93ece..ed6ab0c37 100644 --- a/troposphere/static/js/collections/APITokenCollection.js +++ b/troposphere/static/js/collections/APITokenCollection.js @@ -4,8 +4,8 @@ import globals from "globals"; export default Backbone.Collection.extend({ model: APIToken, - url: globals.API_V2_ROOT + "/ssh_keys", + url: globals.API_V2_ROOT + "/access_tokens", parse: function(data) { - return [{name: "Home CLI", id: 2}]; + return data.results; } }); diff --git a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx index 2c6b85e9f..0c52a6035 100644 --- a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx +++ b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx @@ -37,11 +37,12 @@ export default React.createClass({ this.setState({ isSubmitting: true }); + var response = actions.APITokenActions.create(attributes); setTimeout(() => { this.setState({ isSubmitting: false, successView: true, - hash: "343rw24fg983498j3urfu39" + hash: response.changed.token }); }, 2000); }, diff --git a/troposphere/static/js/components/settings/advanced/TokenListView.jsx b/troposphere/static/js/components/settings/advanced/TokenListView.jsx index ce7c346f3..fb2497906 100644 --- a/troposphere/static/js/components/settings/advanced/TokenListView.jsx +++ b/troposphere/static/js/components/settings/advanced/TokenListView.jsx @@ -88,7 +88,6 @@ const APITokenConfiguration = React.createClass({ let {APITokenStore} = this.props.subscriptions, profile = this.state.profile, api_token = APITokenStore.getAll(); - debugger; return (

Personal Access Tokens

diff --git a/troposphere/static/js/models/APIToken.js b/troposphere/static/js/models/APIToken.js index 0a737decf..492373fff 100644 --- a/troposphere/static/js/models/APIToken.js +++ b/troposphere/static/js/models/APIToken.js @@ -2,5 +2,5 @@ import Backbone from "backbone"; import globals from "globals"; export default Backbone.Model.extend({ - urlRoot: globals.API_V2_ROOT + "/ssh_keys" + urlRoot: globals.API_V2_ROOT + "/access_tokens" }); diff --git a/troposphere/static/js/stores/APITokenStore.js b/troposphere/static/js/stores/APITokenStore.js index cce1796cc..c6a70e371 100644 --- a/troposphere/static/js/stores/APITokenStore.js +++ b/troposphere/static/js/stores/APITokenStore.js @@ -15,15 +15,15 @@ Dispatcher.register(function(dispatch) { switch (actionType) { case APITokenConstants.ADD_TOKEN: - store.add(payload.sshKey); + store.add(payload.apiToken); break; case APITokenConstants.REMOVE_TOKEN: - store.remove(payload.sshKey); + store.remove(payload.apiToken); break; case APITokenConstants.UPDATE_TOKEN: - store.update(payload.sshKey); + store.update(payload.apiToken); break; default: From 4930e1c5692ad3c53acbfc1cbc6870e3d9b5fdc8 Mon Sep 17 00:00:00 2001 From: Calvin Mclean Date: Fri, 10 Aug 2018 15:15:50 -0700 Subject: [PATCH 03/14] Enable APIToken editing and deleting --- .../modals/api_token/APITokenDelete.jsx | 19 +++++++++++++++++-- .../modals/api_token/APITokenEdit.jsx | 4 +++- troposphere/static/js/stores/APITokenStore.js | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx b/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx index 24a666933..1eee0934d 100644 --- a/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx +++ b/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx @@ -5,16 +5,29 @@ import actions from "actions"; import {RaisedButton} from "material-ui"; import WarningIcon from "material-ui/svg-icons/alert/warning"; -export default React.createClass({ +import subscribe from "utilities/subscribe"; + +const APITokenDelete = React.createClass({ propTypes: { token: React.PropTypes.instanceOf(Backbone.Model), - user: React.PropTypes.number.isRequired }, mixins: [BootstrapModalMixin], onSubmit() { this.hide(); + let token = this.props.token; + let {APITokenStore} = this.props.subscriptions; + APITokenStore.remove(token); + token.destroy({ + success: function() { + APITokenStore.emitChange(); + }, + error: function() { + APITokenStore.add(token); + APITokenStore.emitChange(); + } + }); }, render() { @@ -58,3 +71,5 @@ export default React.createClass({ ); } }); + +export default subscribe(APITokenDelete, ["APITokenStore"]); diff --git a/troposphere/static/js/components/modals/api_token/APITokenEdit.jsx b/troposphere/static/js/components/modals/api_token/APITokenEdit.jsx index 020fbb6fc..ea65172f2 100644 --- a/troposphere/static/js/components/modals/api_token/APITokenEdit.jsx +++ b/troposphere/static/js/components/modals/api_token/APITokenEdit.jsx @@ -7,7 +7,7 @@ import {RaisedButton} from "material-ui"; export default React.createClass({ mixins: [BootstrapModalMixin], propTypes: { - token: React.PropTypes.number.isRequired + token: React.PropTypes.object.isRequired }, getInitialState() { @@ -25,7 +25,9 @@ export default React.createClass({ }, onSubmit() { + let token = this.props.token; const {name} = this.state; + actions.APITokenActions.update(token, { name: name.trim() }); this.hide(); }, diff --git a/troposphere/static/js/stores/APITokenStore.js b/troposphere/static/js/stores/APITokenStore.js index c6a70e371..520fcf2d8 100644 --- a/troposphere/static/js/stores/APITokenStore.js +++ b/troposphere/static/js/stores/APITokenStore.js @@ -8,6 +8,7 @@ var APITokenStore = BaseStore.extend({ }); let store = new APITokenStore(); + Dispatcher.register(function(dispatch) { var actionType = dispatch.action.actionType; var payload = dispatch.action.payload; From a0f4060de002dc7a42a753c1b6477433875f1134 Mon Sep 17 00:00:00 2001 From: Calvin Mclean Date: Fri, 10 Aug 2018 16:52:28 -0700 Subject: [PATCH 04/14] Return token to the user without using setTimeout --- troposphere/static/js/actions/APITokenActions.js | 3 ++- .../static/js/components/modals/api_token/APITokenCreate.jsx | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/troposphere/static/js/actions/APITokenActions.js b/troposphere/static/js/actions/APITokenActions.js index 854321c1a..5eb52478c 100644 --- a/troposphere/static/js/actions/APITokenActions.js +++ b/troposphere/static/js/actions/APITokenActions.js @@ -3,7 +3,7 @@ import APIToken from "models/APIToken"; import Utils from "./Utils"; export default { - create: ({name, atmo_user}) => { + create: ({name, atmo_user}, callback) => { if (!name) throw new Error("Missing Token name"); if (!atmo_user) throw new Error("Missing Token author"); let apiToken = new APIToken({ @@ -18,6 +18,7 @@ export default { .save() .done(() => { Utils.dispatch(APITokenConstants.UPDATE_TOKEN, {apiToken}); + callback(apiToken); }) .fail(() => { Utils.dispatch(APITokenConstants.REMOVE_TOKEN, {apiToken}); diff --git a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx index 0c52a6035..8af96ac02 100644 --- a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx +++ b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx @@ -37,14 +37,13 @@ export default React.createClass({ this.setState({ isSubmitting: true }); - var response = actions.APITokenActions.create(attributes); - setTimeout(() => { + actions.APITokenActions.create(attributes, (response) => { this.setState({ isSubmitting: false, successView: true, hash: response.changed.token }); - }, 2000); + }); }, renderFormView() { From 2983c70729ac99890bc7ed9f64cd783eb1b2dc56 Mon Sep 17 00:00:00 2001 From: Calvin Mclean Date: Mon, 13 Aug 2018 13:52:33 -0700 Subject: [PATCH 05/14] Handle failed attempts to create a token If the Atmosphere API fails to create a token, the modal would still act as if it was trying to create the token. Now, it will go back to the initial state of the modal (while still showing the entered name), and will present a notification with an error message. --- .../static/js/actions/APITokenActions.js | 11 ++++++++-- .../modals/api_token/APITokenCreate.jsx | 21 +++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/troposphere/static/js/actions/APITokenActions.js b/troposphere/static/js/actions/APITokenActions.js index 5eb52478c..83eb94bf5 100644 --- a/troposphere/static/js/actions/APITokenActions.js +++ b/troposphere/static/js/actions/APITokenActions.js @@ -1,9 +1,10 @@ import APITokenConstants from "constants/APITokenConstants"; import APIToken from "models/APIToken"; +import NotificationController from "controllers/NotificationController"; import Utils from "./Utils"; export default { - create: ({name, atmo_user}, callback) => { + create: ({name, atmo_user}, successCallback, failCallback) => { if (!name) throw new Error("Missing Token name"); if (!atmo_user) throw new Error("Missing Token author"); let apiToken = new APIToken({ @@ -18,10 +19,16 @@ export default { .save() .done(() => { Utils.dispatch(APITokenConstants.UPDATE_TOKEN, {apiToken}); - callback(apiToken); + successCallback(apiToken); }) .fail(() => { Utils.dispatch(APITokenConstants.REMOVE_TOKEN, {apiToken}); + NotificationController.error( + "Error creating token.", + "Your login might be expired. If you continue to see this error " + + "after logging in again, contact support." + ); + failCallback(apiToken); }); return apiToken; }, diff --git a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx index 8af96ac02..2fa10ca13 100644 --- a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx +++ b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx @@ -37,12 +37,21 @@ export default React.createClass({ this.setState({ isSubmitting: true }); - actions.APITokenActions.create(attributes, (response) => { - this.setState({ - isSubmitting: false, - successView: true, - hash: response.changed.token - }); + actions.APITokenActions.create(attributes, this.successCallback, this.failCallback); + }, + + successCallback(response) { + this.setState({ + isSubmitting: false, + successView: true, + hash: response.changed.token + }); + }, + + failCallback(response) { + this.setState({ + isSubmitting: false, + successView: false }); }, From fa2b6f88d6ae520339eb08333eabf5a625676d85 Mon Sep 17 00:00:00 2001 From: tharon-c Date: Wed, 15 Aug 2018 20:48:29 +0000 Subject: [PATCH 06/14] Fix lint errors and format --- troposphere/static/js/actions/APITokenActions.js | 2 +- .../js/components/modals/api_token/APITokenCreate.jsx | 9 +++++---- .../js/components/modals/api_token/APITokenDelete.jsx | 11 +++++++---- .../js/components/modals/api_token/APITokenEdit.jsx | 3 +-- .../js/components/settings/advanced/TokenListView.jsx | 2 -- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/troposphere/static/js/actions/APITokenActions.js b/troposphere/static/js/actions/APITokenActions.js index 83eb94bf5..3eae4bfea 100644 --- a/troposphere/static/js/actions/APITokenActions.js +++ b/troposphere/static/js/actions/APITokenActions.js @@ -26,7 +26,7 @@ export default { NotificationController.error( "Error creating token.", "Your login might be expired. If you continue to see this error " + - "after logging in again, contact support." + "after logging in again, contact support." ); failCallback(apiToken); }); diff --git a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx index 2fa10ca13..b8729aa80 100644 --- a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx +++ b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx @@ -1,11 +1,9 @@ import React from "react"; -import Backbone from "backbone"; 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"; -import Code from "components/common/ui/Code"; export default React.createClass({ mixins: [BootstrapModalMixin], @@ -37,7 +35,11 @@ export default React.createClass({ this.setState({ isSubmitting: true }); - actions.APITokenActions.create(attributes, this.successCallback, this.failCallback); + actions.APITokenActions.create( + attributes, + this.successCallback, + this.failCallback + ); }, successCallback(response) { @@ -155,7 +157,6 @@ export default React.createClass({ render() { const {successView, isSubmitting} = this.state; - const {edit} = this.props; return (
diff --git a/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx b/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx index 1eee0934d..015ce6fb0 100644 --- a/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx +++ b/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx @@ -1,15 +1,13 @@ import React from "react"; import Backbone from "backbone"; import BootstrapModalMixin from "components/mixins/BootstrapModalMixin"; -import actions from "actions"; 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), + token: React.PropTypes.instanceOf(Backbone.Model) }, mixins: [BootstrapModalMixin], @@ -47,7 +45,12 @@ const APITokenDelete = React.createClass({ style={{minHeight: "300px"}} className="modal-body">
- {" "} + {" "}

{`Are you sure you want to delete Access Token "${name}"? Any applications using this Token will not be able to connect to your account`}

diff --git a/troposphere/static/js/components/modals/api_token/APITokenEdit.jsx b/troposphere/static/js/components/modals/api_token/APITokenEdit.jsx index ea65172f2..a8ac95ba2 100644 --- a/troposphere/static/js/components/modals/api_token/APITokenEdit.jsx +++ b/troposphere/static/js/components/modals/api_token/APITokenEdit.jsx @@ -1,5 +1,4 @@ import React from "react"; -import Backbone from "backbone"; import BootstrapModalMixin from "components/mixins/BootstrapModalMixin"; import actions from "actions"; import {RaisedButton} from "material-ui"; @@ -27,7 +26,7 @@ export default React.createClass({ onSubmit() { let token = this.props.token; const {name} = this.state; - actions.APITokenActions.update(token, { name: name.trim() }); + actions.APITokenActions.update(token, {name: name.trim()}); this.hide(); }, diff --git a/troposphere/static/js/components/settings/advanced/TokenListView.jsx b/troposphere/static/js/components/settings/advanced/TokenListView.jsx index fb2497906..129d7e15d 100644 --- a/troposphere/static/js/components/settings/advanced/TokenListView.jsx +++ b/troposphere/static/js/components/settings/advanced/TokenListView.jsx @@ -5,8 +5,6 @@ import APITokenDelete from "components/modals/api_token/APITokenDelete"; import APITokenEdit from "components/modals/api_token/APITokenEdit"; import subscribe from "utilities/subscribe"; -import globals from "globals"; - const APITokenConfiguration = React.createClass({ getInitialState() { let {ProfileStore} = this.props.subscriptions; From 8c5f1a276d8f2656931bf349e736db66a8499de5 Mon Sep 17 00:00:00 2001 From: tharon-c Date: Wed, 15 Aug 2018 20:59:14 +0000 Subject: [PATCH 07/14] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df1294ad3..410d99f18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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)) From 42a8db5667cf744b13aa1bc2d7e8f1d39b9f1af9 Mon Sep 17 00:00:00 2001 From: tharon-c Date: Wed, 15 Aug 2018 21:18:34 +0000 Subject: [PATCH 08/14] hide APITokenCreate modal if error --- .../static/js/components/modals/api_token/APITokenCreate.jsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx index b8729aa80..d7eecc89f 100644 --- a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx +++ b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx @@ -51,10 +51,7 @@ export default React.createClass({ }, failCallback(response) { - this.setState({ - isSubmitting: false, - successView: false - }); + this.hide(); }, renderFormView() { From d26a397399e0f03a7d3a06293bfd6f8b5aa93545 Mon Sep 17 00:00:00 2001 From: tharon-c Date: Fri, 17 Aug 2018 17:47:45 +0000 Subject: [PATCH 09/14] refactor for readability and code style --- .../static/js/actions/APITokenActions.js | 6 +-- .../modals/api_token/APITokenCreate.jsx | 8 ++-- .../settings/advanced/TokenListView.jsx | 44 ++++++------------- 3 files changed, 21 insertions(+), 37 deletions(-) diff --git a/troposphere/static/js/actions/APITokenActions.js b/troposphere/static/js/actions/APITokenActions.js index 3eae4bfea..c86fcb51a 100644 --- a/troposphere/static/js/actions/APITokenActions.js +++ b/troposphere/static/js/actions/APITokenActions.js @@ -4,12 +4,12 @@ import NotificationController from "controllers/NotificationController"; import Utils from "./Utils"; export default { - create: ({name, atmo_user}, successCallback, failCallback) => { + create: ({name, atmoUser}, successCallback, failCallback) => { if (!name) throw new Error("Missing Token name"); - if (!atmo_user) throw new Error("Missing Token author"); + if (!atmoUser) throw new Error("Missing Token author"); let apiToken = new APIToken({ name, - atmo_user + atmo_user: atmoUser }); // Add token optimistically diff --git a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx index d7eecc89f..e565a9dd1 100644 --- a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx +++ b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx @@ -14,8 +14,10 @@ export default React.createClass({ getInitialState() { return { errorMsg: "", - name: this.props.name || "", - successView: false + name: "", + hash: "", + successView: false, + isSubmitting: false }; }, @@ -30,7 +32,7 @@ export default React.createClass({ const {user} = this.props; let attributes = { name: name.trim(), - atmo_user: user + atmoUser: user }; this.setState({ isSubmitting: true diff --git a/troposphere/static/js/components/settings/advanced/TokenListView.jsx b/troposphere/static/js/components/settings/advanced/TokenListView.jsx index 129d7e15d..ce54201fa 100644 --- a/troposphere/static/js/components/settings/advanced/TokenListView.jsx +++ b/troposphere/static/js/components/settings/advanced/TokenListView.jsx @@ -14,36 +14,22 @@ const APITokenConfiguration = React.createClass({ }; }, - updateState() { - this.setState(this.getInitialState()); - }, launchDeleteModal(token) { - ModalHelpers.renderModal( - APITokenDelete, - { - token - }, - () => {} - ); + ModalHelpers.renderModal(APITokenDelete, { + token + }); }, + launchCreateModal(user) { - ModalHelpers.renderModal( - APITokenCreate, - { - user - }, - () => {} - ); + ModalHelpers.renderModal(APITokenCreate, { + user + }); }, + launchEditModal(token) { - ModalHelpers.renderModal( - APITokenEdit, - { - token, - edit: true - }, - () => {} - ); + ModalHelpers.renderModal(APITokenEdit, { + token + }); }, style() { @@ -61,8 +47,6 @@ const APITokenConfiguration = React.createClass({ renderTokenRow(apiToken) { let {td} = this.style(); - // Set a key that lexicograhically sorts first by title then by cid. - // Cannot sort by id, because recently created bootscript has no id let key = apiToken.get("name") + apiToken.cid; return ( @@ -85,7 +69,7 @@ const APITokenConfiguration = React.createClass({ render() { let {APITokenStore} = this.props.subscriptions, profile = this.state.profile, - api_token = APITokenStore.getAll(); + apiTokens = APITokenStore.getAll(); return (

Personal Access Tokens

@@ -108,9 +92,7 @@ const APITokenConfiguration = React.createClass({ - {api_token - ? api_token.map(this.renderTokenRow) - : []} + {(apiTokens || []).map(this.renderTokenRow)} Date: Fri, 17 Aug 2018 19:55:36 +0000 Subject: [PATCH 10/14] Use actions over accessing store directly --- .../static/js/actions/APITokenActions.js | 19 +++++++++++++++++++ .../modals/api_token/APITokenDelete.jsx | 13 ++----------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/troposphere/static/js/actions/APITokenActions.js b/troposphere/static/js/actions/APITokenActions.js index c86fcb51a..2fe9fbca6 100644 --- a/troposphere/static/js/actions/APITokenActions.js +++ b/troposphere/static/js/actions/APITokenActions.js @@ -51,5 +51,24 @@ export default { 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; } }; diff --git a/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx b/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx index 015ce6fb0..959ebebb9 100644 --- a/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx +++ b/troposphere/static/js/components/modals/api_token/APITokenDelete.jsx @@ -1,5 +1,6 @@ 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"; @@ -15,17 +16,7 @@ const APITokenDelete = React.createClass({ onSubmit() { this.hide(); let token = this.props.token; - let {APITokenStore} = this.props.subscriptions; - APITokenStore.remove(token); - token.destroy({ - success: function() { - APITokenStore.emitChange(); - }, - error: function() { - APITokenStore.add(token); - APITokenStore.emitChange(); - } - }); + actions.APITokenActions.destroy(token); }, render() { From 5dfcbf8155d9df94726c8f2edb54e8f90852405e Mon Sep 17 00:00:00 2001 From: tharon-c Date: Tue, 21 Aug 2018 23:06:15 +0000 Subject: [PATCH 11/14] Use Promise over Callback --- .../static/js/actions/APITokenActions.js | 35 +++++++++---------- .../modals/api_token/APITokenCreate.jsx | 18 ++++------ 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/troposphere/static/js/actions/APITokenActions.js b/troposphere/static/js/actions/APITokenActions.js index 2fe9fbca6..915a807cb 100644 --- a/troposphere/static/js/actions/APITokenActions.js +++ b/troposphere/static/js/actions/APITokenActions.js @@ -4,33 +4,30 @@ import NotificationController from "controllers/NotificationController"; import Utils from "./Utils"; export default { - create: ({name, atmoUser}, successCallback, failCallback) => { + create: (name, userId) => { if (!name) throw new Error("Missing Token name"); - if (!atmoUser) throw new Error("Missing Token author"); + if (!userId) throw new Error("Missing Token author"); let apiToken = new APIToken({ name, - atmo_user: atmoUser + atmo_user: userId }); // Add token optimistically Utils.dispatch(APITokenConstants.ADD_TOKEN, {apiToken}); - apiToken - .save() - .done(() => { - Utils.dispatch(APITokenConstants.UPDATE_TOKEN, {apiToken}); - successCallback(apiToken); - }) - .fail(() => { - Utils.dispatch(APITokenConstants.REMOVE_TOKEN, {apiToken}); - NotificationController.error( - "Error creating token.", - "Your login might be expired. If you continue to see this error " + - "after logging in again, contact support." - ); - failCallback(apiToken); - }); - return apiToken; + let promise = Promise.resolve(apiToken.save()); + promise.then(() => { + Utils.dispatch(APITokenConstants.UPDATE_TOKEN, {apiToken}); + }); + promise.catch(() => { + Utils.dispatch(APITokenConstants.REMOVE_TOKEN, {apiToken}); + 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); diff --git a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx index e565a9dd1..4b96a1a42 100644 --- a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx +++ b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx @@ -30,29 +30,23 @@ export default React.createClass({ onSubmit() { const {name} = this.state; const {user} = this.props; - let attributes = { - name: name.trim(), - atmoUser: user - }; this.setState({ isSubmitting: true }); - actions.APITokenActions.create( - attributes, - this.successCallback, - this.failCallback - ); + let promise = actions.APITokenActions.create(name.trim(), user); + promise.then(this.onSuccess); + promise.catch(this.onError); }, - successCallback(response) { + onSuccess(response) { this.setState({ isSubmitting: false, successView: true, - hash: response.changed.token + hash: response.token }); }, - failCallback(response) { + onError(response) { this.hide(); }, From f96c6541ae44c68467f546be3cf6d0762b126f93 Mon Sep 17 00:00:00 2001 From: tharon-c Date: Wed, 22 Aug 2018 21:37:29 +0000 Subject: [PATCH 12/14] Don't update optimistically --- troposphere/static/js/actions/APITokenActions.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/troposphere/static/js/actions/APITokenActions.js b/troposphere/static/js/actions/APITokenActions.js index 915a807cb..e036b912e 100644 --- a/troposphere/static/js/actions/APITokenActions.js +++ b/troposphere/static/js/actions/APITokenActions.js @@ -12,15 +12,11 @@ export default { atmo_user: userId }); - // Add token optimistically - Utils.dispatch(APITokenConstants.ADD_TOKEN, {apiToken}); - let promise = Promise.resolve(apiToken.save()); promise.then(() => { - Utils.dispatch(APITokenConstants.UPDATE_TOKEN, {apiToken}); + Utils.dispatch(APITokenConstants.ADD_TOKEN, {apiToken}); }); promise.catch(() => { - Utils.dispatch(APITokenConstants.REMOVE_TOKEN, {apiToken}); NotificationController.error( "Error creating token.", "Your login might be expired. If you continue to see this error " + From 9a8dea984208926d0a1a8444c6e049c9b76cb2d9 Mon Sep 17 00:00:00 2001 From: tharon-c Date: Thu, 23 Aug 2018 18:52:17 +0000 Subject: [PATCH 13/14] Don't send user in request --- .../static/js/actions/APITokenActions.js | 6 +- .../modals/api_token/APITokenCreate.jsx | 6 +- .../instance/InstanceLaunchWizardModal.jsx | 84 +++++++++---------- .../launch/components/BasicInfoForm.jsx | 6 +- .../settings/advanced/TokenListView.jsx | 26 +----- 5 files changed, 52 insertions(+), 76 deletions(-) diff --git a/troposphere/static/js/actions/APITokenActions.js b/troposphere/static/js/actions/APITokenActions.js index e036b912e..3b3985f6e 100644 --- a/troposphere/static/js/actions/APITokenActions.js +++ b/troposphere/static/js/actions/APITokenActions.js @@ -4,12 +4,10 @@ import NotificationController from "controllers/NotificationController"; import Utils from "./Utils"; export default { - create: (name, userId) => { + create: name => { if (!name) throw new Error("Missing Token name"); - if (!userId) throw new Error("Missing Token author"); let apiToken = new APIToken({ - name, - atmo_user: userId + name }); let promise = Promise.resolve(apiToken.save()); diff --git a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx index 4b96a1a42..97845af1f 100644 --- a/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx +++ b/troposphere/static/js/components/modals/api_token/APITokenCreate.jsx @@ -7,9 +7,6 @@ import CopyButton from "components/common/ui/CopyButton"; export default React.createClass({ mixins: [BootstrapModalMixin], - propTypes: { - user: React.PropTypes.number.isRequired - }, getInitialState() { return { @@ -29,11 +26,10 @@ export default React.createClass({ onSubmit() { const {name} = this.state; - const {user} = this.props; this.setState({ isSubmitting: true }); - let promise = actions.APITokenActions.create(name.trim(), user); + let promise = actions.APITokenActions.create(name.trim()); promise.then(this.onSuccess); promise.catch(this.onError); }, diff --git a/troposphere/static/js/components/modals/instance/InstanceLaunchWizardModal.jsx b/troposphere/static/js/components/modals/instance/InstanceLaunchWizardModal.jsx index 31a96746c..a3a5bad47 100644 --- a/troposphere/static/js/components/modals/instance/InstanceLaunchWizardModal.jsx +++ b/troposphere/static/js/components/modals/instance/InstanceLaunchWizardModal.jsx @@ -43,7 +43,7 @@ export default React.createClass({ image: React.PropTypes.instanceOf(Backbone.Model), project: React.PropTypes.instanceOf(Backbone.Model), onConfirm: React.PropTypes.func.isRequired, - initialView: React.PropTypes.string.isRequired + initialView: React.PropTypes.string.isRequired, }, getInitialState: function() { @@ -81,7 +81,7 @@ export default React.createClass({ identityProvider: null, attachedScripts: [], allocationSource: null, - waitingOnLaunch: false + waitingOnLaunch: false, }; }, @@ -113,7 +113,7 @@ export default React.createClass({ let imageVersionList; if (this.state.image) { imageVersionList = stores.ImageVersionStore.fetchWhere({ - image_id: this.state.image.id + image_id: this.state.image.id, }); } @@ -138,10 +138,10 @@ export default React.createClass({ let identityProvider, providerSizeList; if (provider) { identityProvider = stores.IdentityStore.findOne({ - "provider.id": provider.id + "provider.id": provider.id, }); providerSizeList = stores.SizeStore.fetchWhere({ - provider__id: provider.id + provider__id: provider.id, }); } @@ -173,7 +173,7 @@ export default React.createClass({ provider, providerSize, identityProvider, - allocationSource + allocationSource, }); }, @@ -210,31 +210,31 @@ export default React.createClass({ viewImageSelect: function() { this.setState({ - view: "IMAGE_VIEW" + view: "IMAGE_VIEW", }); }, viewProject: function() { this.setState({ - view: "PROJECT_VIEW" + view: "PROJECT_VIEW", }); }, viewBasic: function() { this.setState({ - view: "BASIC_VIEW" + view: "BASIC_VIEW", }); }, viewAdvanced: function() { this.setState({ - view: "ADVANCED_VIEW" + view: "ADVANCED_VIEW", }); }, viewLicense: function() { this.setState({ - view: "LICENSE_VIEW" + view: "LICENSE_VIEW", }); }, @@ -250,7 +250,7 @@ export default React.createClass({ } let imageVersionList = stores.ImageVersionStore.fetchWhere({ - image_id: image.id + image_id: image.id, }); let imageVersion; @@ -274,10 +274,10 @@ export default React.createClass({ let identityProvider, providerSizeList; if (provider) { identityProvider = stores.IdentityStore.findOne({ - "provider.id": provider.id + "provider.id": provider.id, }); providerSizeList = stores.SizeStore.fetchWhere({ - provider__id: provider.id + provider__id: provider.id, }); } @@ -301,7 +301,7 @@ export default React.createClass({ provider, imageVersion, providerSize, - identityProvider + identityProvider, }, this.viewBasic ); @@ -313,14 +313,14 @@ export default React.createClass({ onNameChange: function(e) { this.setState({ - instanceName: e.target.value + instanceName: e.target.value, }); }, onNameBlur: function(e) { let instanceName = this.state.instanceName.trim(); this.setState({ - instanceName + instanceName, }); }, @@ -337,10 +337,10 @@ export default React.createClass({ let identityProvider, providerSizeList; if (provider) { identityProvider = stores.IdentityStore.findOne({ - "provider.id": provider.id + "provider.id": provider.id, }); providerSizeList = stores.SizeStore.fetchWhere({ - provider__id: provider.id + provider__id: provider.id, }); } @@ -361,19 +361,19 @@ export default React.createClass({ imageVersion, provider, providerSize, - identityProvider + identityProvider, }); }, onProjectChange: function(project) { this.setState({ - project + project, }); }, onAllocationSourceChange: function(source) { this.setState({ - allocationSource: source + allocationSource: source, }); }, @@ -383,7 +383,7 @@ export default React.createClass({ let provider = stores.ProviderStore.findWhere({id: providerId}); let providerSizeList = stores.SizeStore.fetchWhere({ - provider__id: providerId + provider__id: providerId, }); let imageVersion = this.state.imageVersion; @@ -402,13 +402,13 @@ export default React.createClass({ this.setState({ provider, providerSize, - identityProvider + identityProvider, }); }, onProviderChange: function(provider) { let providerSizeList = stores.SizeStore.fetchWhere({ - provider__id: provider.id + provider__id: provider.id, }); let imageVersion = this.state.imageVersion; @@ -426,7 +426,7 @@ export default React.createClass({ } let identityProvider = stores.IdentityStore.findOne({ - "provider.id": provider.id + "provider.id": provider.id, }); if (providerSizeList) { @@ -436,20 +436,20 @@ export default React.createClass({ this.setState({ provider, providerSize, - identityProvider + identityProvider, }); }, onSizeChange: function(providerSize) { this.setState({ - providerSize + providerSize, }); }, onRequestResources: function() { this.hide(); modals.HelpModals.requestMoreResources({ - identity: this.state.identityProvider.id + identity: this.state.identityProvider.id, }); }, @@ -457,7 +457,7 @@ export default React.createClass({ let attachedScripts = this.state.attachedScripts; if (attachedScripts.indexOf(value) === -1) { this.setState({ - attachedScripts: [...attachedScripts, value] + attachedScripts: [...attachedScripts, value], }); } }, @@ -466,7 +466,7 @@ export default React.createClass({ let attachedScripts = this.state.attachedScripts.filter(i => i != item); this.setState({ - attachedScripts + attachedScripts, }); }, @@ -476,7 +476,7 @@ export default React.createClass({ onClearAdvanced: function() { this.setState({ - attachedScripts: [] + attachedScripts: [], }); }, @@ -484,13 +484,13 @@ export default React.createClass({ this.viewBasic(); actions.ProjectActions.create({ name: name, - description + description, }); }, onLaunchFailed: function() { this.setState({ - waitingOnLaunch: false + waitingOnLaunch: false, }); }, @@ -541,7 +541,7 @@ export default React.createClass({ }, onFail: () => { this.onLaunchFailed(); - } + }, }; if (globals.USE_ALLOCATION_SOURCES) { @@ -555,7 +555,7 @@ export default React.createClass({ // enter into a "waiting" state to determine // result of launch operation this.setState({ - waitingOnLaunch: true + waitingOnLaunch: true, }); return; @@ -564,7 +564,7 @@ export default React.createClass({ // if we cannot launch, we are in a world of hurt // - show some indication of that this.setState({ - showValidationErr: true + showValidationErr: true, }); }, @@ -641,7 +641,7 @@ export default React.createClass({ "identityProvider", "providerSize", "imageVersion", - "attachedScripts" + "attachedScripts", ]; // Check if we are using AllocationSource and add to requierd fields @@ -729,7 +729,7 @@ export default React.createClass({ let imageVersionList; if (this.state.image) { imageVersionList = stores.ImageVersionStore.fetchWhere({ - image_id: this.state.image.id + image_id: this.state.image.id, }); if (imageVersionList) { @@ -749,7 +749,7 @@ export default React.createClass({ resourcesUsed = stores.InstanceStore.getTotalResources(provider.id); providerSizeList = stores.SizeStore.fetchWhere({ - provider__id: provider.id + provider__id: provider.id, }); } @@ -807,7 +807,7 @@ export default React.createClass({ hasAdvancedOptions: this.hasAdvancedOptions(), allocationSource: this.state.allocationSource, allocationSourceList, - waitingOnLaunch + waitingOnLaunch, }} /> ); @@ -862,5 +862,5 @@ export default React.createClass({
); - } + }, }); diff --git a/troposphere/static/js/components/modals/instance/launch/components/BasicInfoForm.jsx b/troposphere/static/js/components/modals/instance/launch/components/BasicInfoForm.jsx index 838223f97..818ca8c9a 100644 --- a/troposphere/static/js/components/modals/instance/launch/components/BasicInfoForm.jsx +++ b/troposphere/static/js/components/modals/instance/launch/components/BasicInfoForm.jsx @@ -17,7 +17,7 @@ export default React.createClass({ instanceName: React.PropTypes.string, onNameChange: React.PropTypes.func, onVersionChange: React.PropTypes.func, - onProjectChange: React.PropTypes.func + onProjectChange: React.PropTypes.func, }, componentDidMount: function() { @@ -60,7 +60,7 @@ export default React.createClass({ projectList, instanceName, showValidationErr, - waitingOnLaunch + waitingOnLaunch, } = this.props; let hasErrorClass; let errorMessage = null; @@ -139,5 +139,5 @@ export default React.createClass({
); - } + }, }); diff --git a/troposphere/static/js/components/settings/advanced/TokenListView.jsx b/troposphere/static/js/components/settings/advanced/TokenListView.jsx index ce54201fa..1d2f33ae3 100644 --- a/troposphere/static/js/components/settings/advanced/TokenListView.jsx +++ b/troposphere/static/js/components/settings/advanced/TokenListView.jsx @@ -6,24 +6,14 @@ import APITokenEdit from "components/modals/api_token/APITokenEdit"; import subscribe from "utilities/subscribe"; const APITokenConfiguration = React.createClass({ - getInitialState() { - let {ProfileStore} = this.props.subscriptions; - var profile = ProfileStore.get(); - return { - profile - }; - }, - launchDeleteModal(token) { ModalHelpers.renderModal(APITokenDelete, { token }); }, - launchCreateModal(user) { - ModalHelpers.renderModal(APITokenCreate, { - user - }); + launchCreateModal() { + ModalHelpers.renderModal(APITokenCreate); }, launchEditModal(token) { @@ -68,7 +58,6 @@ const APITokenConfiguration = React.createClass({ render() { let {APITokenStore} = this.props.subscriptions, - profile = this.state.profile, apiTokens = APITokenStore.getAll(); return (
@@ -95,11 +84,7 @@ const APITokenConfiguration = React.createClass({ {(apiTokens || []).map(this.renderTokenRow)} - + @@ -114,7 +99,4 @@ const APITokenConfiguration = React.createClass({ } }); -export default subscribe(APITokenConfiguration, [ - "APITokenStore", - "ProfileStore" -]); +export default subscribe(APITokenConfiguration, ["APITokenStore"]); From 2029ac88b34870d6ad796b5e84a01e0a7999bdf6 Mon Sep 17 00:00:00 2001 From: Tharon-C Date: Wed, 20 Jun 2018 19:05:41 +0000 Subject: [PATCH 14/14] Stub out provision option on Instance Launch --- .../instance/InstanceLaunchWizardModal.jsx | 7 +++++ .../launch/components/BasicInfoForm.jsx | 26 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/troposphere/static/js/components/modals/instance/InstanceLaunchWizardModal.jsx b/troposphere/static/js/components/modals/instance/InstanceLaunchWizardModal.jsx index a3a5bad47..dbbc3ee66 100644 --- a/troposphere/static/js/components/modals/instance/InstanceLaunchWizardModal.jsx +++ b/troposphere/static/js/components/modals/instance/InstanceLaunchWizardModal.jsx @@ -80,6 +80,7 @@ export default React.createClass({ providerSize: null, identityProvider: null, attachedScripts: [], + provisionOption: "full-provision", allocationSource: null, waitingOnLaunch: false, }; @@ -242,6 +243,10 @@ export default React.createClass({ // Event Handlers //========================= + onProvisionOptionChange: function(e, value) { + this.setState({provisionOption: value}); + }, + onSelectImage: function(image) { let instanceName = image.get("name"); @@ -792,6 +797,7 @@ export default React.createClass({ onAllocationSourceChange: this.onAllocationSourceChange, onIdentityChange: this.onIdentityChange, onProviderChange: this.onProviderChange, + onProvisionOptionChange: this.onProvisionOptionChange, onRequestResources: this.onRequestResources, onSizeChange: this.onSizeChange, onSubmitLaunch: this.onSubmitLaunch, @@ -802,6 +808,7 @@ export default React.createClass({ providerList, providerSize, providerSizeList, + provisionOption: this.state.provisionOption, resourcesUsed, viewAdvanced: this.viewAdvanced, hasAdvancedOptions: this.hasAdvancedOptions(), diff --git a/troposphere/static/js/components/modals/instance/launch/components/BasicInfoForm.jsx b/troposphere/static/js/components/modals/instance/launch/components/BasicInfoForm.jsx index 818ca8c9a..de0921516 100644 --- a/troposphere/static/js/components/modals/instance/launch/components/BasicInfoForm.jsx +++ b/troposphere/static/js/components/modals/instance/launch/components/BasicInfoForm.jsx @@ -4,6 +4,7 @@ import Backbone from "backbone"; import context from "context"; import featureFlags from "utilities/featureFlags"; import SelectMenu from "components/common/ui/SelectMenu"; +import {RadioButton, RadioButtonGroup} from "material-ui/RadioButton"; export default React.createClass({ propTypes: { @@ -59,9 +60,11 @@ export default React.createClass({ project, projectList, instanceName, + provisionOption, showValidationErr, waitingOnLaunch, } = this.props; + let hasErrorClass; let errorMessage = null; @@ -137,6 +140,29 @@ export default React.createClass({ {projectType}

+
+ + + + + Minimum Provision
(This might break + functionality) + + } + /> +
+
); },