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 (