diff --git a/.gitignore b/.gitignore index 24a52cdae6..464522616b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ Icon node_modules/ .tmp dist + +docker-compose-prod.yml diff --git a/Dockerfile.dev b/Dockerfile.dev index fd95a85a17..8486df3aec 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -10,6 +10,7 @@ WORKDIR /usr/src/app COPY package.json /usr/src/app/ RUN npm install RUN npm install -g grunt +RUN npm install -g npm-check-updates # Bundle app source COPY . /usr/src/app diff --git a/Gruntfile.js b/Gruntfile.js index 8a54f5a921..bd9cd9d4aa 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -28,6 +28,7 @@ module.exports = function (grunt) { options: { hot: true, port: 8000, + host: "0.0.0.0", webpack: webpackDevConfig, publicPath: '/assets/', contentBase: './<%= pkg.src %>/', @@ -108,6 +109,17 @@ module.exports = function (grunt) { } }, + cacheBust: { + taskName: { + options: { + baseDir: '<%= pkg.dist %>', + assets: ['assets/**'], + deleteOriginals: true, + }, + src: ['<%= pkg.dist %>/index.html'] + } + }, + clean: { dist: { options: { @@ -137,7 +149,7 @@ module.exports = function (grunt) { grunt.registerTask('test', ['karma']); - grunt.registerTask('build', ['copy', 'webpack']); + grunt.registerTask('build', ['copy', 'webpack', 'cacheBust']); grunt.registerTask('default', []); }; diff --git a/Makefile b/Makefile index ec4383a813..fa9fa7d8a7 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,10 @@ docker-build-dev: docker-clean-build-dev: docker build -t happa-dev --no-cache -f Dockerfile.dev . -# Run tests (of which there are non right now) +# Print a list of outdated dependencies +npm-check-updates: + docker run -ti happa-dev ncu + +# Run tests (of which there are none right now) test: docker-build docker run -ti -p 8000:8000 -v ${PWD}/src:/usr/src/app/src happa-dev npm test \ No newline at end of file diff --git a/README.md b/README.md index af0315f582..12ae60406e 100644 --- a/README.md +++ b/README.md @@ -26,15 +26,23 @@ Running tests No tests at the moment though -Building for production ------------------------ +Building / Running for production +---------------------------------- -Build the production docker container with: +`make production` will build and run happa's production container. -`make production` +Happa makes use of a development container to produce production assets. +A production container then takes those assets and serves them using nginx. + +The build process is as follows: + +0. Build the development container `make docker-build-dev` + +1. Create production assets using the development container, save them in the +dist folder. `make dist` + +2. Create the production container `make docker-build-prod` -You'll want to do this if you want to test the production container locally. -It creates a container called `happa` Configuration ------------- @@ -73,4 +81,10 @@ The `` line in index.html is what includes the file for us. More information about our font kit and how to use it can be found here: -https://fortawesome.com/kits/d940f7eb/docs \ No newline at end of file +https://fortawesome.com/kits/d940f7eb/docs + + +Checking for outdated dependencies +---------------------------------- + +To see what dependencies have updates run `make npm-check-updates` \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 8c067e89ac..2264fec935 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: build: context: ./ dockerfile: Dockerfile.dev - image: happa:latest + image: happa-dev:latest command: npm start ports: - "8000:8000" @@ -19,15 +19,26 @@ services: - "5000:5000" environment: DEBUGGING: 1 - CORS_ORIGIN: http://docker.dev:8000 + HAPPA_BASE_URI: http://docker.dev:8000 GIANTSWARM_API_URI: http://giantswarmmockapi:8000 GIANTSWARM_API_TOKEN: valid_token HUBSPOT_API_URI: http://hubspotmock:9999 HUBSPOT_HUB_ID: "430224" HUBSPOT_API_KEY: 1234567890abcdefghijklmnop + #NUM_PROXIES: "0" + RATELIMIT_GLOBAL: 1000 per day, 100 per hour, 30 per minute + RATELIMIT_STORAGE_URL: redis://redis:6379 + links: - hubspotmock - giantswarmmockapi + - redis:redis + - mailcatcher:mailcatcher + + redis: + image: redis:3.2 + ports: + - "6379:6379" # Mocks the HubSpot API hubspotmock: @@ -40,3 +51,9 @@ services: image: registry.giantswarm.io/giantswarm/mock-api:latest ports: - "9000:8000" + + mailcatcher: + image: registry.giantswarm.io/giantswarm/mailcatcher:latest + ports: + - "1080:1080" + - "1025:1025" diff --git a/karma.conf.js b/karma.conf.js index 789e004ca8..cab0321ea9 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -67,7 +67,7 @@ module.exports = function (config) { // - Safari (only Mac) // - PhantomJS // - IE (only Windows) - browsers: ['PhantomJS'], + browsers: [], reporters: ['progress'], captureTimeout: 60000, singleRun: true diff --git a/package.json b/package.json index ed4fe01b76..6f5a05dcda 100644 --- a/package.json +++ b/package.json @@ -15,33 +15,29 @@ "mainOutput": "main", "dependencies": { "babel-plugin-transform-react-jsx": "~6.8.0", - "codemirror": "~5.14.2", + "codemirror": "~5.16.0", "copy-to-clipboard": "~3.0.2", - "jquery": "2.2.4", "jshint": "^2.5.0", "marked": "~0.3.5", "node-kubernetes-client": "~0.2.3", "node-sass": "^3.7.0", - "normalize.css": "~4.1.1", - "react": "~15.0.1", - "react-addons-css-transition-group": "~15.0.2", - "giantswarm": "git://github.com/giantswarm/giantswarm-js-client#no-es6", + "normalize.css": "~4.2.0", + "react": "~15.2.0", + "react-addons-css-transition-group": "~15.2.0", + "giantswarm": "git://github.com/giantswarm/giantswarm-js-client", "react-codemirror": "~0.2.6", - "react-dom": "~15.0.2", + "react-dom": "~15.2.0", "react-multistep": "~2.1.0", - "react-router": "~2.4.0", + "react-router": "~2.5.2", "react-router-scroll": "0.2.0", "reflux": "~0.4.1", - "rest": "~1.3.2", "underscore": "~1.8.3", "validate.js": "~0.10.0", "superagent-bluebird-promise": "^3.0.0", "superagent": "1.8.2", - "file-loader": "0.8.5", - "phantomjs-prebuilt": "2.1.7", + "file-loader": "0.9.0", "bufferutil": "1.2.1", "jasmine-core": "2.4.1", - "validate.js": "^0.9.0", "platform": "1.3.1" }, "devDependencies": { @@ -54,21 +50,19 @@ "grunt-contrib-clean": "~1.0.0", "grunt-contrib-connect": "~1.0.2", "grunt-contrib-copy": "~1.0.0", - "grunt-karma": "~1.0.0", + "grunt-karma": "~2.0.0", "grunt-open": "~0.2.3", + "grunt-cache-bust": "1.3.0", "grunt-webpack": "~1.0.11", "jshint-loader": "~0.8.3", "jsxhint-loader": "~0.2.0", - "karma": "~0.13.22", - "karma-chrome-launcher": "~1.0.1", - "karma-firefox-launcher": "~1.0.0", + "karma": "~1.1.1", "karma-jasmine": "~1.0.2", - "karma-phantomjs-launcher": "~1.0.0", "karma-script-launcher": "~1.0.0", "karma-webpack": "~1.7.0", "load-grunt-tasks": "~3.5.0", "react-hot-loader": "~1.3.0", - "sass-loader": "~3.2.0", + "sass-loader": "~4.0.0", "style-loader": "~0.13.1", "url-loader": "~0.5.7", "webpack": "~1.13.0", diff --git a/src/components/reflux_actions/cluster_actions.js b/src/actions/cluster_actions.js similarity index 96% rename from src/components/reflux_actions/cluster_actions.js rename to src/actions/cluster_actions.js index 4dcb91750d..6015dd112c 100644 --- a/src/components/reflux_actions/cluster_actions.js +++ b/src/actions/cluster_actions.js @@ -1,6 +1,6 @@ "use strict"; var Reflux = require('reflux'); -var GiantSwarm = require('../utils/giantswarm_client_wrapper'); +var GiantSwarm = require('../lib/giantswarm_client_wrapper'); var _ = require('underscore'); var ClusterActions = Reflux.createActions([ diff --git a/src/components/reflux_actions/flash_message_actions.js b/src/actions/flash_message_actions.js similarity index 100% rename from src/components/reflux_actions/flash_message_actions.js rename to src/actions/flash_message_actions.js diff --git a/src/actions/forgot_password_actions.js b/src/actions/forgot_password_actions.js new file mode 100644 index 0000000000..1e8e4776e3 --- /dev/null +++ b/src/actions/forgot_password_actions.js @@ -0,0 +1,69 @@ +"use strict"; +var Reflux = require('reflux'); + +var Passage = require("../lib/passage_client"); +var passage = new Passage({endpoint: window.config.passageEndpoint}); + +var Actions = Reflux.createActions([ + // Request recovery form + "updateEmail", + {"requestPasswordRecoveryToken": {children: ["completed", "failed"]}}, + + // Update password form + {"verifyPasswordRecoveryToken": {children: ["completed", "failed"]}}, + {"passwordEditing": {children: ["started", "completed"]}}, + {"passwordConfirmationEditing": {children: ["started", "completed"]}}, + {"setNewPassword": {children: ["completed", "failed"]}} +]); + +Actions.requestPasswordRecoveryToken.listen(function(email) { + var action = this; + + try { + passage.requestPasswordRecoveryToken({email}) + .then(data => { + action.completed(data); + }) + .catch(error => { + action.failed(error); + }); + } catch(error) { + action.failed(error); + } +}); + +Actions.verifyPasswordRecoveryToken.listen(function(email, token) { + var action = this; + + try { + passage.verifyPasswordRecoveryToken({email, token}) + .then(data => { + action.completed(data); + }) + .catch(error => { + action.failed(error); + }); + } catch(error) { + action.failed(error); + } +}); + +Actions.setNewPassword.listen(function(email, token, password) { + var action = this; + + try { + passage.setNewPassword({email, token, password}) + .then(data => { + action.completed(data); + }) + .catch(error => { + action.failed(error); + }); + } catch(error) { + console.log(error); + action.failed(error); + } +}); + + +module.exports = Actions; \ No newline at end of file diff --git a/src/components/reflux_actions/sign_up_form_actions.js b/src/actions/sign_up_form_actions.js similarity index 94% rename from src/components/reflux_actions/sign_up_form_actions.js rename to src/actions/sign_up_form_actions.js index 318a1ac593..c416932360 100644 --- a/src/components/reflux_actions/sign_up_form_actions.js +++ b/src/actions/sign_up_form_actions.js @@ -1,7 +1,7 @@ "use strict"; var Reflux = require('reflux'); -var Passage = require("../../lib/passage_client"); +var Passage = require("../lib/passage_client"); var passage = new Passage({endpoint: window.config.passageEndpoint}); var Actions = Reflux.createActions([ diff --git a/src/components/reflux_actions/user_actions.js b/src/actions/user_actions.js similarity index 97% rename from src/components/reflux_actions/user_actions.js rename to src/actions/user_actions.js index b577918898..cfe189b470 100644 --- a/src/components/reflux_actions/user_actions.js +++ b/src/actions/user_actions.js @@ -1,6 +1,6 @@ "use strict"; var Reflux = require('reflux'); -var GiantSwarm = require('../utils/giantswarm_client_wrapper'); +var GiantSwarm = require('../lib/giantswarm_client_wrapper'); var UserActions = Reflux.createActions([ "updateEmail", diff --git a/src/components/app.js b/src/components/app.js index 6730f670e1..098c958d9f 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -5,17 +5,17 @@ var ReactRouter, {applyRouterMiddleware, Router, Route, IndexRoute, NotFoundRout var useScroll = require('react-router-scroll'); var render = require('react-dom').render; -var Layout = require('./layout'); -var newService = require('./new_service/index'); -var docs = require('./docs/index'); -var login = require('./login/index'); -var logout = require('./logout/index'); -var signup = require('./signup/index'); -var notFound = require('./not_found/index'); -window.Passage = require('../lib/passage_client'); - -var UserActions = require('./reflux_actions/user_actions'); -var UserStore = require('./reflux_stores/user_store'); +var Layout = require('./layout'); +var docs = require('./docs/index'); +var login = require('./login/index'); +var logout = require('./logout/index'); +var signup = require('./signup/index'); +var notFound = require('./not_found/index'); +var forgot_password_index = require('./forgot_password/index'); +var forgot_password_set_password = require('./forgot_password/set_password'); + +var UserActions = require('../actions/user_actions'); +var UserStore = require('../stores/user_store'); require('normalize.css'); require('../styles/app.scss'); @@ -37,13 +37,14 @@ render(( + + - diff --git a/src/components/docs/2_configure_kubectl.js b/src/components/docs/2_configure_kubectl.js index 99f1baaadf..0bb4f00044 100644 --- a/src/components/docs/2_configure_kubectl.js +++ b/src/components/docs/2_configure_kubectl.js @@ -5,8 +5,8 @@ var Slide = require('../component_slider/slide'); var Markdown = require('./markdown'); var {CodeBlock, Prompt, Output} = require('./codeblock'); var FileBlock = require('./fileblock'); -var ClusterStore = require('../reflux_stores/cluster_store.js'); -var ClusterActions = require('../reflux_actions/cluster_actions.js'); +var ClusterStore = require('../../stores/cluster_store.js'); +var ClusterActions = require('../../actions/cluster_actions.js'); module.exports = React.createClass ({ mixins: [Reflux.connect(ClusterStore,'clusters'), Reflux.listenerMixin], diff --git a/src/components/docs/3_simple_example.js b/src/components/docs/3_simple_example.js index 3a2db5e3b2..d148fecb38 100644 --- a/src/components/docs/3_simple_example.js +++ b/src/components/docs/3_simple_example.js @@ -5,8 +5,8 @@ var Slide = require('../component_slider/slide'); var Markdown = require('./markdown'); var {CodeBlock, Prompt, Output} = require('./codeblock'); var FileBlock = require('./fileblock'); -var ClusterStore = require('../reflux_stores/cluster_store.js'); -var ClusterActions = require('../reflux_actions/cluster_actions.js'); +var ClusterStore = require('../../stores/cluster_store.js'); +var ClusterActions = require('../../actions/cluster_actions.js'); module.exports = React.createClass ({ mixins: [Reflux.connect(ClusterStore,'clusters'), Reflux.listenerMixin], @@ -77,7 +77,7 @@ module.exports = React.createClass ({

Save the above manifest in a file called helloworld-manifest.yaml.

-

If you're new to Kubernetes: A manifest describes things to create in Kubernetes. In this case the manifest describes two different things, a service and a deployment. The service is there to expose containers (here: the ones with the label app: helloworld) inside your cluster via a certain hostname and port. The deployment describes your helloworld deployment. It manages a replica set, which ensures that a number of pods (two, actually) containing Docker containers from a certain image are running.

+

If you're new to Kubernetes: A manifest describes things to create in Kubernetes. In this case the manifest describes two different things, a service and a deployment. The service is there to expose containers (here: the ones with the label app: helloworld) inside your cluster via a certain hostname and port. The deployment describes your helloworld deployment. It manages a replica set, which ensures that a number of pods (two, actually) containing Docker containers from a certain image are running.

Now use kubectl to create the service and the deployment:

diff --git a/src/components/docs/codeblock.js b/src/components/docs/codeblock.js index 18c3039d4d..9eaa4bcf13 100644 --- a/src/components/docs/codeblock.js +++ b/src/components/docs/codeblock.js @@ -25,7 +25,7 @@ var copy = require('copy-to-clipboard'); var _ = require('underscore'); var Line = require("./line"); var ReactCSSTransitionGroup = require('react-addons-css-transition-group'); -var Helpers = require('../helpers'); +var Helpers = require('../../lib/helpers'); var Prompt = React.createClass ({ render: function() { diff --git a/src/components/docs/fileblock.js b/src/components/docs/fileblock.js index 3f82c0279e..6cbde3cc46 100644 --- a/src/components/docs/fileblock.js +++ b/src/components/docs/fileblock.js @@ -19,11 +19,10 @@ var Modernizr = window.Modernizr; var React = require('react'); var copy = require('copy-to-clipboard'); -var $ = require('jquery'); var _ = require('underscore'); var Line = require("./line"); var ReactCSSTransitionGroup = require('react-addons-css-transition-group'); -var Helpers = require('../helpers'); +var Helpers = require('../../lib/helpers'); module.exports = React.createClass ({ getInitialState: function() { @@ -37,12 +36,6 @@ module.exports = React.createClass ({ copy(Helpers.dedent(this.props.children)); - var copyConfirmation = $(this.refs.confirmCopy); - copyConfirmation.addClass('visible'); - setTimeout(function() { - copyConfirmation.removeClass('visible'); - }, 500); - this.setState({clicked: false}); }, diff --git a/src/components/flash_messages/index.js b/src/components/flash_messages/index.js index 8d833b3868..2dabc38573 100644 --- a/src/components/flash_messages/index.js +++ b/src/components/flash_messages/index.js @@ -1,7 +1,7 @@ "use strict"; -var flashActions = require('../reflux_actions/flash_message_actions'); -var flashStore = require('../reflux_stores/flash_message_store'); +var flashActions = require('../../actions/flash_message_actions'); +var flashStore = require('../../stores/flash_message_store'); var Reflux = require('reflux'); var React = require('react'); var ReactCSSTransitionGroup = require('react-addons-css-transition-group'); @@ -30,7 +30,7 @@ module.exports = React.createClass({ render: function() { return (
- + { _.map(flashStore.getAll(), this.makeFlashComponent) }
diff --git a/src/components/forgot_password/index.js b/src/components/forgot_password/index.js new file mode 100644 index 0000000000..8a5f847ea1 --- /dev/null +++ b/src/components/forgot_password/index.js @@ -0,0 +1,104 @@ +"use strict"; + +var forgotPasswordStore = require('../../stores/forgot_password_store'); +var forgotPasswordActions = require('../../actions/forgot_password_actions'); +var flashMessageActions = require('../../actions/flash_message_actions'); +var FlashMessages = require('../flash_messages/index.js'); +var Reflux = require('reflux'); +var React = require('react'); +var ReactCSSTransitionGroup = require('react-addons-css-transition-group'); +var {Link} = require('react-router'); + +module.exports = React.createClass({ + mixins: [Reflux.connect(forgotPasswordStore, 'form'), Reflux.listenerMixin], + + getInitialState: function() { + return { + email: forgotPasswordStore.getInitialState().email + }; + }, + + submit: function(event) { + event.preventDefault(); + flashMessageActions.clearAll(); + forgotPasswordActions.requestPasswordRecoveryToken(this.state.form.email); + }, + + componentWillUnmount: function() { + flashMessageActions.clearAll(); + }, + + updateEmail(event) { + flashMessageActions.clearAll(); + forgotPasswordActions.updateEmail(event.target.value); + this.setState({ + email: event.target.value + }); + }, + + success() { + return( +
+

Check your mail!

+

If you have an account, we've sent an email to {this.state.form.email}.

+ + +

Having trouble? Please contact us via support@giantswarm.io

+
+ + + Back to login form + +
+ ); + }, + + form() { + return( +
+

Forgot your password?

+

Enter the email you used to sign-up and submit the form. We'll send you a link you can use to set a new password.

+
+
+ + +
+
+ + + { + this.state.form.submitting ? : null + } + +
+ Back to login form +
+
+ ); + }, + + render: function() { + return ( +
+
+ + +
+
+ +
+ { this.state.form.tokenRequested ? this.success() : this.form() } +
+
+
+ ); + } +}); \ No newline at end of file diff --git a/src/components/forgot_password/set_password.js b/src/components/forgot_password/set_password.js new file mode 100644 index 0000000000..e7eb83c856 --- /dev/null +++ b/src/components/forgot_password/set_password.js @@ -0,0 +1,171 @@ +"use strict"; + +var forgotPasswordStore = require('../../stores/forgot_password_store'); +var forgotPasswordActions = require('../../actions/forgot_password_actions'); +var userActions = require('../../actions/user_actions'); +var flashMessageActions = require('../../actions/flash_message_actions'); +var FlashMessages = require('../flash_messages/index.js'); +var Reflux = require('reflux'); +var React = require('react'); +var ReactCSSTransitionGroup = require('react-addons-css-transition-group'); +var {Link} = require('react-router'); +var PasswordField = require("../signup/password_field"); +var StatusMessage = require('../signup/status_message'); + +module.exports = React.createClass({ + contextTypes: { + router: React.PropTypes.object + }, + + mixins: [Reflux.connect(forgotPasswordStore, 'form'), Reflux.listenerMixin], + + getInitialState: function() { + return { + password: "", + passwordConfirmation: "", + email: forgotPasswordStore.getInitialState().email + }; + }, + + + componentDidMount: function(){ + // If we have the email, verify the token immediately. + if (this.state.form.email) { + forgotPasswordActions.verifyPasswordRecoveryToken(this.state.form.email, this.props.params.token); + } + + this.listenTo(userActions.authenticate.completed, this.onAuthenticateCompleted); + }, + + onAuthenticateCompleted: function() { + flashMessageActions.clearAll(); + this.context.router.push('/'); + flashMessageActions.add({ + message: 'Password set successfully! Welcome back!', + class: "success" + }); + }, + + submit: function(event) { + event.preventDefault(); + forgotPasswordActions.setNewPassword(this.state.form.email, this.props.params.token, this.state.form.passwordField.value); + }, + + setEmail: function(event) { + event.preventDefault(); + flashMessageActions.clearAll(); + forgotPasswordActions.updateEmail(this.state.email); + forgotPasswordActions.verifyPasswordRecoveryToken(this.state.email, this.props.params.token); + }, + + updateEmail(event) { + flashMessageActions.clearAll(); + + this.setState({ + email: event.target.value + }); + }, + + setPasswordForm() { + if (this.state.form.tokenValid) { + return( +
+ + +
+ +
+ +
+ +
+
+ + + { + this.state.form.submitting ? : null + } + +
+ Back to login form + + ); + } else { + if (this.state.form.verifyingToken) { + return( +
+ +
+ Validating your token... +
+ ); + } else { + return( +
+ Something went wrong. +
+ ); + } + } + }, + + setEmailForm() { + return( +
+

Before we can check your recovery token, please type in your email again for verification purposes.

+
+ + +
+ +
+ + + { + this.state.form.submitting ? : null + } + +
+ Back to login form
+ Request a new token +
+ ); + }, + + render: function() { + return ( +
+
+ + +
+
+ +
+

Set your new password

+ {this.state.form.email ? this.setPasswordForm() : this.setEmailForm()} +
+
+
+ ); + } +}); \ No newline at end of file diff --git a/src/components/idi.js b/src/components/idi.js deleted file mode 100644 index 53a93a7ee4..0000000000 --- a/src/components/idi.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -var rest = require('rest'); -var mime = require('rest/interceptor/mime'); -var client = rest.wrap(mime); - -module.exports = function() { - const IDI_URL = 'https://idi.giantswarm.io/'; - - this.analyze = function(imageName) { - return client({ - path: IDI_URL + imageName - }).then(function(response) { - return response.entity; - }); - }; -}; \ No newline at end of file diff --git a/src/components/layout.js b/src/components/layout.js index 12c1a27211..fb4bcccc16 100644 --- a/src/components/layout.js +++ b/src/components/layout.js @@ -4,8 +4,8 @@ var React = require('react'); var Reflux = require('reflux'); var {Link, IndexLink} = require('react-router'); -var UserActions = require('./reflux_actions/user_actions'); -var UserStore = require('./reflux_stores/user_store'); +var UserActions = require('../actions/user_actions'); +var UserStore = require('../stores/user_store'); var FlashMessages = require('./flash_messages/index'); module.exports = React.createClass ({ diff --git a/src/components/login/index.js b/src/components/login/index.js index ba09ad2d17..472b2b5c3c 100644 --- a/src/components/login/index.js +++ b/src/components/login/index.js @@ -1,12 +1,13 @@ "use strict"; -var actions = require('../reflux_actions/user_actions'); -var store = require('../reflux_stores/user_store'); -var flashMessageActions = require('../reflux_actions/flash_message_actions'); +var actions = require('../../actions/user_actions'); +var store = require('../../stores/user_store'); +var flashMessageActions = require('../../actions/flash_message_actions'); var FlashMessages = require('../flash_messages/index.js'); var Reflux = require('reflux'); var React = require('react'); var ReactCSSTransitionGroup = require('react-addons-css-transition-group'); +var {Link} = require('react-router'); module.exports = React.createClass({ contextTypes: { @@ -20,6 +21,10 @@ module.exports = React.createClass({ this.listenTo(actions.authenticate.failed, this.onAuthenticateFailed); }, + componentWillUnmount: function() { + flashMessageActions.clearAll(); + }, + onAuthenticateCompleted: function() { flashMessageActions.clearAll(); this.context.router.push('/'); @@ -48,6 +53,7 @@ module.exports = React.createClass({ logIn(e) { e.preventDefault(); + flashMessageActions.clearAll(); if ( ! this.state.user.email) { @@ -79,7 +85,7 @@ module.exports = React.createClass({ -

Giant Swarm Web UI

+

Log in to Giant Swarm

@@ -111,7 +117,9 @@ module.exports = React.createClass({ }
+ Forgot your password?
+
By logging in you acknowledge that we track your activities in order to analyze your product usage and improve your experience. See our Privacy Policy.
diff --git a/src/components/logout/index.js b/src/components/logout/index.js index 2d6eedf43a..701c322d23 100644 --- a/src/components/logout/index.js +++ b/src/components/logout/index.js @@ -1,8 +1,8 @@ "use strict"; -var actions = require('../reflux_actions/user_actions'); -var flashMessageActions = require('../reflux_actions/flash_message_actions'); -var store = require('../reflux_stores/user_store'); +var actions = require('../../actions/user_actions'); +var flashMessageActions = require('../../actions/flash_message_actions'); +var store = require('../../stores/user_store'); var Reflux = require('reflux'); var React = require('react'); var ReactCSSTransitionGroup = require('react-addons-css-transition-group'); diff --git a/src/components/lorry.js b/src/components/lorry.js deleted file mode 100644 index 804cda1ef1..0000000000 --- a/src/components/lorry.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -var rest = require('rest'); -var mime = require('rest/interceptor/mime'); -var client = rest.wrap(mime); - -module.exports = function() { - const LORRY_URL = 'http://lorry.gigantic.io/validation'; - - this.validate = function(document) { - return client({ - path: LORRY_URL, - entity: JSON.stringify({ document: document }) - }).then(function(response) { - if (response.status.code === 200) { - return response.entity; - } else { - return { - status: "invalid", - errors: [{error: {message: "YAML is so invalid that lorry exploded"}}] - }; - } - }); - }; -}; \ No newline at end of file diff --git a/src/components/new_service/index.js b/src/components/new_service/index.js deleted file mode 100644 index 6241a93b6d..0000000000 --- a/src/components/new_service/index.js +++ /dev/null @@ -1,42 +0,0 @@ -"use strict"; - -// Create Service Wizard -// Sets up a ComponentSlider with the steps to create a service -// -// Steps know how to validate themselves and let the wizard know -// when to continue or go back through onContinue and onPrevious -// -// Interacts with NewServiceStore, which is what holds the NewService -// as it is being built. - -var React = require('react'); -var ComponentSlider = require('../component_slider'); - -var StepDefine = require('./step_define.js'); -var StepImages = require('./step_images.js'); -var StepConfigure = require('./step_configure.js'); -var StepLaunch = require('./step_launch.js'); - -module.exports = React.createClass({ - // TODO: Make this into actions that update this components currentSlide state - onContinue() { - this.refs.componentSlider.next(); - }, - - onPrevious() { - this.refs.componentSlider.previous(); - }, - - slides() { - return ([ - , - , - , - - ]); - }, - - render: function() { - return ; - } -}); \ No newline at end of file diff --git a/src/components/new_service/step_configure.js b/src/components/new_service/step_configure.js deleted file mode 100644 index f4537272fa..0000000000 --- a/src/components/new_service/step_configure.js +++ /dev/null @@ -1,92 +0,0 @@ -'use strict'; -var React = require('react'); -var Slide = require('../component_slider/slide'); -var actions = require('../reflux_actions/new_service_actions'); -var store = require('../reflux_stores/new_service_store'); -var Reflux = require('reflux'); -var _ = require('underscore'); - -module.exports = React.createClass ({ - mixins: [Reflux.connect(store,'newService')], - - componentWillMount: function() { - var firstComponent = _.map(this.state.newService.parsedCompose, function(componentData, componentName) { - return componentName; - })[0]; - - this.setState({ - activeComponent: firstComponent - }); - }, - - validate(){ - this.props.onContinue(); - }, - - selectComponent(componentName) { - // TODO: Turn this into an action? - // not sure. Maybe this is ok because it is state specific to the visual - // representation of this component and not really the newService we - // are making. - this.setState({ - activeComponent: componentName - }); - }, - - isActiveComponent(componentName) { - if (this.state.activeComponent === componentName) { - return "active"; - } - }, - - truncate(string, maxLength) { - if(string.length > maxLength) { - return string.substring(0,maxLength-1)+"\u2026"; - } else { - return string; - } - }, - - updateComponentRamLimit(componentName, event) { - var minimum = event.target.value; - actions.componentRamLimitEdited(componentName, minimum); - }, - - render() { - return ( - -

Configure your containers

-
-
    - { - _.map(this.state.newService.parsedCompose, function(componentData, componentName) { - return ( -
  • - {this.truncate(componentName, 12)} -
  • - ); - }.bind(this)) - } -
- -
-

Memory Usage Limit

- How much RAM does your container need? -
- MB -
-
-
-
- -
- -
- ); - } -}); \ No newline at end of file diff --git a/src/components/new_service/step_define.js b/src/components/new_service/step_define.js deleted file mode 100644 index bb8cf862ac..0000000000 --- a/src/components/new_service/step_define.js +++ /dev/null @@ -1,126 +0,0 @@ -'use strict'; -var React = require('react'); -var Codemirror = require('react-codemirror'); -var yaml = require('codemirror/mode/yaml/yaml'); -var ReactCSSTransitionGroup = require('react-addons-css-transition-group'); - -var Slide = require('../component_slider/slide'); -var actions = require('../reflux_actions/new_service_actions'); -var store = require('../reflux_stores/new_service_store'); -var Reflux = require('reflux'); - -module.exports = React.createClass ({ - mixins: [Reflux.connect(store,'newService'), Reflux.listenerMixin], - - componentDidMount: function() { - this.listenTo(actions.validateServiceDefinition.completed, this.onValidateServiceDefinitionCompleted); - - var codeMirror = this.refs.codeMirror.getCodeMirror(); - - function makeMarker() { - var marker = document.createElement("div"); - marker.style.color = "#d22"; - marker.innerHTML = "●"; - return marker; - } - - codeMirror.on("gutterClick", function(cm, n) { - var info = cm.lineInfo(n); - cm.setGutterMarker(n, "breakpoints", info.gutterMarkers ? null : makeMarker()); - }); - }, - - getInitialState: function() { - return { - buttonText: "Submit Definition" - }; - }, - - // TODO: Figure out how to write this in a way where the view does not listen to actions - // only the store should be doing that. - onValidateServiceDefinitionCompleted: function(validationResult) { - if (validationResult.status === "valid") { - this.props.onContinue(); - } else { - this.setState({validating: false}); - } - }, - - updateServiceName(event) { - actions.serviceNameEdited(event.target.value); - }, - - updateCode (newCode) { - actions.serviceDefinitionEdited(newCode); - }, - - validate(event){ - event.preventDefault(); - this.setState({validating: true}); - actions.validateServiceDefinition(this.state.newService.fields.rawComposeYaml.value); - }, - - hasErrors(fieldName) { - return this.state.newService.fields[fieldName].validationErrors && this.state.newService.fields[fieldName].validationErrors.length > 0; - }, - - updateFileSelect(evt) { - var files = evt.target.files, - reader = new FileReader(); - reader.onload = function() { - actions.serviceDefinitionEdited(this.result); - }; - reader.readAsText(files[0]); - }, - - // TODO: Extract into components (like the text field with error states and validation) - - render() { - return ( - -

Define your service

-
-
- - -
- { - this.hasErrors('serviceName') ? {this.state.newService.fields.serviceName.validationErrors} : null - } -
-
- - -
- - -
- { - this.hasErrors('rawComposeYaml') ? {this.state.newService.fields.rawComposeYaml.validationErrors} : null - } -
-
- -
- -
- - - { - this.state.validating ? : null - } - -
-
- ); - } -}); \ No newline at end of file diff --git a/src/components/new_service/step_images.js b/src/components/new_service/step_images.js deleted file mode 100644 index 685516a9a0..0000000000 --- a/src/components/new_service/step_images.js +++ /dev/null @@ -1,78 +0,0 @@ -'use strict'; -var React = require('react'); -var ReactCSSTransitionGroup = require('react-addons-css-transition-group'); -var Slide = require('../component_slider/slide'); -var actions = require('../reflux_actions/new_service_actions'); -var store = require('../reflux_stores/new_service_store'); -var Reflux = require('reflux'); -var _ = require('underscore'); - -module.exports = React.createClass ({ - mixins: [Reflux.connect(store,'newService')], - - componentDidMount: function() { - this.listenTo(actions.analyzeImage.completed, this.onAnalyzeImageCompleted); - }, - - onAnalyzeImageCompleted: function(imageName) { - this.continueIfAllImagesDone(); - }, - - onPrevious: function() { - clearTimeout(this.continueTimeout); - this.props.onPrevious(); - }, - - continueIfAllImagesDone: function() { - var allImagesDone = _.every(this.state.newService.images, function(image) { - return image.analyzeStatus === "DONE"; - }); - - if (allImagesDone) { - // Allow the progress bar to reach the end - // before transitioning automatically to the next step - this.continueTimeout = setTimeout(this.props.onContinue, 500); - } - }, - - retryAnalyze: function(imageName) { - actions.analyzeImage(imageName); - }, - - render() { - return ( - -

Analyzing images

- { - this.state.newService.images.map(function(image){ - return ( -
-
{image.name} - {image.analyzeStatus}
- - { - image.analyzeStatus === "STARTED" ? : null - } - -
-
-
-
-
-
- -
-                      {
-                        image.analyzeError ? image.analyzeError.error : null
-                      }
-                    
-
- ); - }.bind(this)) - } - -
- -
- ); - } -}); \ No newline at end of file diff --git a/src/components/new_service/step_launch.js b/src/components/new_service/step_launch.js deleted file mode 100644 index 951f1905f1..0000000000 --- a/src/components/new_service/step_launch.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; -var React = require('react'); -var Slide = require('../component_slider/slide'); - -module.exports = React.createClass ({ - getInitialState() { - return {}; - }, - - validate(){ - // Do some validation - - // Signal continue - this.props.onContinue(); - }, - - render() { - return ( - -

Ready for lift off!

-
- -
- ); - } -}); \ No newline at end of file diff --git a/src/components/reflux_actions/new_service_actions.js b/src/components/reflux_actions/new_service_actions.js deleted file mode 100644 index d2417e705a..0000000000 --- a/src/components/reflux_actions/new_service_actions.js +++ /dev/null @@ -1,51 +0,0 @@ -"use strict"; -var Reflux = require('reflux'); -var Lorry = require('../lorry'); -var IDI = require('../idi'); - -var NewServiceActions = Reflux.createActions([ - "serviceNameEdited", - "serviceDefinitionEdited", - {"validateServiceDefinition": {children: ["completed", "failed"]}}, - {"analyzeImage": {children: ["started", "completed", "failed", "progress"]}}, - "componentRamLimitEdited" -]); - -NewServiceActions.validateServiceDefinition.listen(function(definition) { - var lorry = new Lorry(); - var action = this; - - lorry.validate(definition).then(function(response) { - action.completed(response); - }); -}); - -NewServiceActions.analyzeImage.listen(function(imageName) { - var idi = new IDI(); - var action = this; - var progress = 0; - var n = 0; - var completed = false; - - action.started(imageName); - - var fakeProgressInterval = setInterval(function() { - if (!completed) { - n += 1; - action.progress(imageName, 75 - (75/n)); - } - }, 400 + (Math.random() * 400)); - - idi.analyze(imageName).then(function(response) { - completed = true; - clearInterval(fakeProgressInterval); - action.completed(imageName, response); - }, function(error) { - completed = true; - clearInterval(fakeProgressInterval); - action.failed(imageName, error); - }); -}); - - -module.exports = NewServiceActions; \ No newline at end of file diff --git a/src/components/reflux_stores/new_service_store.js b/src/components/reflux_stores/new_service_store.js deleted file mode 100644 index e965aeb6be..0000000000 --- a/src/components/reflux_stores/new_service_store.js +++ /dev/null @@ -1,133 +0,0 @@ -"use strict"; -var Reflux = require('reflux'); -var actions = require("../reflux_actions/new_service_actions"); -var Lorry = require('../lorry'); -var _ = require('underscore'); -var validate = require('validate.js'); - -var newService = { - fields: { - serviceName: {value: 'my-first-service', validationErrors: []}, - rawComposeYaml: { - value: 'helloworld:\n image: index.docker.io/giantswarm/helloworld\n ports:\n - "8080:8080"', - validationErrors: [] - } - }, - parsedCompose: "NOTPARSEDYET", -}; - -var serviceNameConstraint = { - presence: true, - format: { - pattern: /^[a-zA-Z0-9_\-]*$/, - message: "must only contain letters, numbers, underscores, and hyphens" - } -}; - -module.exports = Reflux.createStore({ - listenables: actions, - - getInitialState: function() { - return newService; - }, - - onServiceNameEdited: function(name) { - var validationErrors = validate.single(name, serviceNameConstraint); - newService.fields.serviceName = {value: name, validationErrors: validationErrors}; - this.trigger(newService); - }, - - onServiceDefinitionEdited: function(definition) { - newService.fields.rawComposeYaml.value = definition; - this.trigger(newService); - }, - - onComponentRamLimitEdited: function(componentName, limit) { - newService.parsedCompose[componentName].ramLimit = limit; - this.trigger(newService); - }, - - - onValidateServiceDefinition: function() { - }, - - onValidateServiceDefinitionCompleted: function(validationResult) { - if (validationResult.errors) { - newService.fields.rawComposeYaml.validationErrors = validationResult.errors.map( - function(errorObject) {return errorObject.error.message;} - ); - } - - newService.parsedCompose = validationResult.document; - - newService.images = _.map(newService.parsedCompose, function(val, key) { - var image = _.findWhere(newService.images, {name: val.image}); - var status = "NOTSTARTED"; - var progress = 0; - - if (image && image.analyzeStatus) { - status = image.analyzeStatus; - } - - if (image && image.analyzeStatus) { - progress = image.analyzeProgress; - } - - if (status === "NOTSTARTED") { - actions.analyzeImage(val.image); - } - - return { - name: val.image, - analyzeProgress: progress, - analyzeStatus: status - }; - }); - - newService.images = _.uniq(newService.images, function isUniq(item) { - return String(item.name); - }); - - newService.parsedCompose = _.mapObject(newService.parsedCompose, function(componentData, componentName) { - componentData.ramLimit = 128; - return componentData; - }); - - this.trigger(newService); - }, - - onAnalyzeImage: function(imageName) { - var image = _.findWhere(newService.images, {name: imageName}); - image.analyzeProgress = 0; - image.analyzeStatus = "STARTING"; - this.trigger(newService); - }, - - onAnalyzeImageStarted: function(imageName) { - var image = _.findWhere(newService.images, {name: imageName}); - image.analyzeStatus = "STARTED"; - this.trigger(newService); - }, - - onAnalyzeImageProgress: function(imageName, progress) { - var image = _.findWhere(newService.images, {name: imageName}); - image.analyzeProgress = progress; - this.trigger(newService); - }, - - onAnalyzeImageCompleted: function(imageName, analyzeResult) { - var image = _.findWhere(newService.images, {name: imageName}); - // Check if there was an unauthorized, set analyzeStatus to UNAUTHORIZED - image.analyzeProgress = 100; - image.analyzeStatus = "DONE"; - this.trigger(newService); - }, - - onAnalyzeImageFailed: function(imageName, error) { - var image = _.findWhere(newService.images, {name: imageName}); - image.analyzeProgress = 0; - image.analyzeStatus = "FAILED"; - image.analyzeError = error; - this.trigger(newService); - } -}); \ No newline at end of file diff --git a/src/components/signup/index.js b/src/components/signup/index.js index f5c12a4bd5..e309650147 100644 --- a/src/components/signup/index.js +++ b/src/components/signup/index.js @@ -2,15 +2,14 @@ var React = require('react'); var Reflux = require('reflux'); -var $ = require('jquery'); var Passage = require("../../lib/passage_client"); var passage = new Passage({endpoint: window.config.passageEndpoint}); -var actions = require("../reflux_actions/sign_up_form_actions"); -var store = require("../reflux_stores/sign_up_form_store"); +var actions = require("../../actions/sign_up_form_actions"); +var store = require("../../stores/sign_up_form_store"); var PasswordField = require("./password_field"); var StatusMessage = require('./status_message'); var TermsOfService = require('./terms_of_service'); -var flashMessageActions = require('../reflux_actions/flash_message_actions'); +var flashMessageActions = require('../../actions/flash_message_actions'); module.exports = React.createClass({ contextTypes: { @@ -23,7 +22,6 @@ module.exports = React.createClass({ // contactID and token are set via URL actions.checkInvite(this.props.params.contactId, this.props.params.token); this.listenTo(actions.advanceForm, this.advanceForm); - this.listenTo(actions.resetForm, this.resetForm); this.listenTo(actions.createAccount.completed, this.accountCreated); }, @@ -33,21 +31,14 @@ module.exports = React.createClass({ }, advanceForm: function() { - $("#" + this.state.signUpForm.formSteps[this.state.signUpForm.currentStep]).slideDown(function() { - if (this.state.signUpForm.currentStep === 1) { - this.refs.password.focus(); - } else if (this.state.signUpForm.currentStep === 2) { - this.refs.passwordConfirmation.focus(); - } else if (this.state.signUpForm.currentStep === 3) { - this.refs.passwordConfirmation.blur(); - } - actions.advanceForm.completed(); - }.bind(this)); - }, - - resetForm: function() { - $("#passwordGroup").hide(); - $("#TOSGroup").hide(); + if (this.state.signUpForm.currentStep === 1) { + this.refs.password.focus(); + } else if (this.state.signUpForm.currentStep === 2) { + this.refs.passwordConfirmation.focus(); + } else if (this.state.signUpForm.currentStep === 3) { + this.refs.passwordConfirmation.blur(); + } + actions.advanceForm.completed(); }, accountCreated: function() { @@ -88,7 +79,7 @@ module.exports = React.createClass({

Create Your Giant Swarm Account

-
+

This is your personal Giant Swarm account for the email address {this.state.signUpForm.email}!

@@ -109,7 +100,7 @@ module.exports = React.createClass({
-