-
{container.Name.replace('/', '')} {state}
+ {this.state.container.Name.replace('/', '')}
-
);
}
});
-module.exports = Container;
+module.exports = ContainerDetails;
diff --git a/app/ContainerModal.react.js b/app/ContainerModal.react.js
new file mode 100644
index 0000000000..aa37a2e846
--- /dev/null
+++ b/app/ContainerModal.react.js
@@ -0,0 +1,93 @@
+var React = require('react');
+var Router = require('react-router');
+var Modal = require('react-bootstrap/Modal');
+var RetinaImage = require('react-retina-image');
+var $ = require('jquery');
+var ContainerStore = require('./ContainerStore.js');
+
+var ContainerModal = React.createClass({
+ getInitialState: function () {
+ return {
+ query: '',
+ results: []
+ };
+ },
+ componentDidMount: function () {
+ this.refs.searchInput.getDOMNode().focus();
+ },
+ search: function (query) {
+ var self = this;
+ $.get('https://registry.hub.docker.com/v1/search?q=' + query, function (result) {
+ self.setState(result);
+ console.log(result);
+ });
+ },
+ handleChange: function (e) {
+ var query = e.target.value;
+
+ if (query === this.state.query) {
+ return;
+ }
+
+ clearTimeout(this.timeout);
+ var self = this;
+ this.timeout = setTimeout(function () {
+ self.search(query);
+ }, 250);
+ },
+ handleClick: function (event) {
+ var name = event.target.getAttribute('name');
+ ContainerStore.create(name);
+ },
+ render: function () {
+ var top = this.state.results.splice(0, 7);
+ var self = this;
+ var results = top.map(function (r) {
+ var name;
+ if (r.is_official) {
+ name =
{r.name};
+ } else {
+ name =
{r.name} ;
+ }
+ return (
+
+
+
+ Create
+
+
+ );
+ });
+ return (
+
+
+
+ );
+ }
+});
+
+module.exports = ContainerModal;
diff --git a/app/ContainerStore.js b/app/ContainerStore.js
new file mode 100644
index 0000000000..973247bf17
--- /dev/null
+++ b/app/ContainerStore.js
@@ -0,0 +1,120 @@
+var EventEmitter = require('events').EventEmitter;
+var async = require('async');
+var assign = require('react/lib/Object.assign');
+var docker = require('./docker.js');
+var $ = require('jquery');
+
+// Merge our store with Node's Event Emitter
+var ContainerStore = assign(EventEmitter.prototype, {
+ _containers: {},
+ init: function () {
+ // TODO: Load cached data from leveldb
+ // Check if the pulled image is working
+
+ // Refresh with docker & hook into events
+ var self = this;
+ this.update(function (err) {
+ docker.client().getEvents(function (err, stream) {
+ stream.setEncoding('utf8');
+ stream.on('data', function (data) {
+ self.update(function (err) {
+
+ });
+ });
+ });
+ });
+ },
+ update: function (callback) {
+ var self = this;
+ docker.client().listContainers({all: true}, function (err, containers) {
+ if (err) {
+ callback(err);
+ return;
+ }
+ async.map(containers, function(container, callback) {
+ docker.client().getContainer(container.Id).inspect(function (err, data) {
+ callback(err, data);
+ });
+ }, function (err, results) {
+ if (err) {
+ callback(err);
+ return;
+ }
+ var containers = {};
+ results.map(function (r) {
+ containers[r.Id] = r;
+ });
+ self._containers = containers;
+ self.emit('change');
+ callback(null);
+ });
+ });
+ },
+ _createContainer: function (image) {
+ docker.client().createContainer({
+ Image: image,
+ Tty: false
+ }, function (err, container) {
+ if (err) {
+ callback(err, null);
+ return;
+ }
+ console.log('Created container: ' + container.id);
+ container.start({
+ PublishAllPorts: true
+ }, function (err) {
+ if (err) { callback(err, null); return; }
+ console.log('Started container: ' + container.id);
+ callback(null, container);
+ });
+ });
+ },
+ create: function (repository, tag, callback) {
+ tag = tag || 'latest';
+ var name = repository + ':' + tag;
+ // Check if image is not local or already being downloaded
+ console.log('Creating container.');
+ var self = this;
+ var image = docker.client().getImage(name);
+ image.inspect(function (err, data) {
+ /*$.get('https://registry.hub.docker.com/v1/repositories/' + repository + '/tags/' + tag, function (data) {
+
+ });*/
+ if (data === null) {
+ // Pull image
+ docker.client().pull(name, function (err, stream) {
+ stream.setEncoding('utf8');
+ stream.on('data', function (data) {
+ console.log(data);
+ });
+ stream.on('end', function () {
+ self._createContainer(name);
+ });
+ });
+
+ // Create placeholder container
+ } else {
+ // If not then directly create the container
+ self._createContainer(name);
+ }
+ });
+ // If so then create a container w/ kitematic-only 'downloading state'
+ // Pull image
+ // When image is done pulling then
+ },
+
+ // Returns all shoes
+ containers: function() {
+ return this._containers;
+ },
+
+ addChangeListener: function(callback) {
+ this.on('change', callback);
+ },
+
+ removeChangeListener: function(callback) {
+ this.removeListener('change', callback);
+ }
+});
+
+module.exports = ContainerStore;
diff --git a/app/Containers.react.js b/app/Containers.react.js
index e8623099c8..c1c81e5274 100644
--- a/app/Containers.react.js
+++ b/app/Containers.react.js
@@ -1,43 +1,75 @@
var React = require('react');
var Router = require('react-router');
+var Modal = require('react-bootstrap/Modal');
+var RetinaImage = require('react-retina-image');
+var ModalTrigger = require('react-bootstrap/ModalTrigger');
+var ContainerModal = require('./ContainerModal.react.js');
+var ContainerStore = require('./ContainerStore.js');
var Route = Router.Route;
var NotFoundRoute = Router.NotFoundRoute;
var DefaultRoute = Router.DefaultRoute;
var Link = Router.Link;
var RouteHandler = Router.RouteHandler;
var Navigation= Router.Navigation;
-
var Header = require('./Header.react.js');
-
var async = require('async');
+var _ = require('underscore');
var docker = require('./docker.js');
-
var ContainerList = React.createClass({
+ mixins: [Navigation],
+ getInitialState: function () {
+ return {
+ containers: []
+ };
+ },
+ handleClick: function () {
+ console.log('hi');
+ },
+ componentDidMount: function () {
+ ContainerStore.addChangeListener(this.update);
+ },
+ componentWillUnmount: function () {
+ ContainerStore.removeChangeListener(this.update);
+ },
+ update: function () {
+ var containers = _.values(ContainerStore.containers()).sort(function (a, b) {
+ return a.Name.localeCompare(b.Name);
+ });
+ console.log(containers);
+ if (containers.length > 0) {
+ this.transitionTo('container', {Id: containers[0].Id, container: containers[0]});
+ }
+ this.setState({
+ containers: containers
+ });
+ },
render: function () {
- var containers = this.props.containers.map(function (container) {
+ var containers = this.state.containers.map(function (container) {
var state;
if (container.State.Running) {
- state =
running ;
- } else if (container.State.Restarting) {
- state =
restarting ;
+ state =
;
+ } else {
+ state =
;
}
-
return (
-
+
-
- {container.Name.replace('/', '')}
-
-
- {state} - {container.Config.Image}
+ {state}
+
+
+ {container.Name.replace('/', '')}
+
+
+ {container.Config.Image}
+
);
});
return (
-
+
);
@@ -45,49 +77,50 @@ var ContainerList = React.createClass({
});
var Containers = React.createClass({
- mixins: [Navigation],
- getInitialState: function() {
- return {containers: [], index: null};
+ getInitialState: function () {
+ return {
+ sidebarOffset: 0
+ };
},
- update: function () {
- var self = this;
- docker.client().listContainers({all: true}, function (err, containers) {
- async.map(containers, function(container, callback) {
- docker.client().getContainer(container.Id).inspect(function (err, data) {
- callback(null, data);
- });
- }, function (err, results) {
- if (results.length > 0) {
- self.transitionTo('container', {Id: results[0].Id, container: results[0]});
- }
- self.setState({containers: results});
+ handleScroll: function (e) {
+ if (e.target.scrollTop > 0 && !this.state.sidebarOffset) {
+ this.setState({
+ sidebarOffset: e.target.scrollTop
});
- });
+ } else if (e.target.scrollTop === 0 && this.state.sidebarOffset) {
+ this.setState({
+ sidebarOffset: 0
+ });
+ }
},
- componentDidMount: function () {
- this.update();
- var self = this;
- docker.client().getEvents(function (err, stream) {
- if (err) {
- throw err;
- }
- stream.setEncoding('utf8');
- stream.on('data', function (data) {
- self.update();
- });
- });
+ handleClick: function () {
+ // ContainerStore.create('jbfink/wordpress', 'latest');
},
render: function () {
+ var sidebarHeaderClass = 'sidebar-header';
+ if (this.state.sidebarOffset) {
+ sidebarHeaderClass += ' sep';
+ }
return (
-
+
);
diff --git a/app/Header.react.js b/app/Header.react.js
index 760dce09d9..b69eb6f18c 100644
--- a/app/Header.react.js
+++ b/app/Header.react.js
@@ -2,6 +2,18 @@ var React = require('react/addons');
var remote = require('remote');
var Header = React.createClass({
+ componentDidMount: function () {
+ document.addEventListener('keyup', this.handleDocumentKeyUp, false);
+ },
+ componentWillUnmount: function () {
+ document.removeEventListener('keyup', this.handleDocumentKeyUp, false);
+ },
+ handleDocumentKeyUp: function (e) {
+ if (e.keyCode === 27 && remote.getCurrentWindow().isFullScreen()) {
+ remote.getCurrentWindow().setFullScreen(false);
+ this.forceUpdate();
+ }
+ },
handleClose: function () {
remote.getCurrentWindow().hide();
},
@@ -9,30 +21,35 @@ var Header = React.createClass({
remote.getCurrentWindow().minimize();
},
handleFullscreen: function () {
- var isFullscreen = remote.getCurrentWindow().isFullScreen();
- remote.getCurrentWindow().setFullScreen(!isFullscreen);
+ remote.getCurrentWindow().setFullScreen(!remote.getCurrentWindow().isFullScreen());
this.forceUpdate();
},
handleFullscreenHover: function () {
this.update();
},
render: function () {
- var fullscreenButton;
+ var buttons;
if (remote.getCurrentWindow().isFullScreen()) {
- fullscreenButton =
;
- } else {
- fullscreenButton =
;
- }
-
- return (
-
-
-
-
- {fullscreenButton}
+ return (
+
-
- );
+ );
+ } else {
+ return (
+
+ );
+ }
}
});
diff --git a/app/boot2docker.js b/app/boot2docker.js
index 23c6feecb3..b788db41a2 100644
--- a/app/boot2docker.js
+++ b/app/boot2docker.js
@@ -20,7 +20,7 @@ var homeDir = function () {
var Boot2Docker = {
version: function () {
- return JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'))['boot2docker-version'];
+ return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'))['boot2docker-version'];
},
cliVersion: function (callback) {
cmdExec([Boot2Docker.command(), 'version'], function (err, out) {
diff --git a/app/docker.js b/app/docker.js
index fc96e8e6bc..ac9701546a 100644
--- a/app/docker.js
+++ b/app/docker.js
@@ -4,15 +4,14 @@ var dockerode = require('dockerode');
var Docker = {
host: null,
+ _client: null,
setHost: function(host) {
this.host = host;
- },
- client: function () {
var certDir = path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], '.boot2docker/certs/boot2docker-vm');
if (!fs.existsSync(certDir)) {
- return null;
+ return;
}
- return new dockerode({
+ this._client = new dockerode({
protocol: 'https',
host: this.host,
port: 2376,
@@ -20,6 +19,9 @@ var Docker = {
cert: fs.readFileSync(path.join(certDir, 'cert.pem')),
key: fs.readFileSync(path.join(certDir, 'key.pem'))
});
+ },
+ client: function () {
+ return this._client;
}
};
diff --git a/app/fonts/clearsans-bold-webfont.ttf b/app/fonts/clearsans-bold-webfont.ttf
new file mode 100755
index 0000000000..710166c79c
Binary files /dev/null and b/app/fonts/clearsans-bold-webfont.ttf differ
diff --git a/app/fonts/clearsans-bolditalic-webfont.ttf b/app/fonts/clearsans-bolditalic-webfont.ttf
new file mode 100755
index 0000000000..1cc7838060
Binary files /dev/null and b/app/fonts/clearsans-bolditalic-webfont.ttf differ
diff --git a/app/fonts/clearsans-italic-webfont.ttf b/app/fonts/clearsans-italic-webfont.ttf
new file mode 100755
index 0000000000..c67d772cb9
Binary files /dev/null and b/app/fonts/clearsans-italic-webfont.ttf differ
diff --git a/app/fonts/clearsans-light-webfont.ttf b/app/fonts/clearsans-light-webfont.ttf
new file mode 100755
index 0000000000..295bf8ef94
Binary files /dev/null and b/app/fonts/clearsans-light-webfont.ttf differ
diff --git a/app/fonts/clearsans-medium-webfont.ttf b/app/fonts/clearsans-medium-webfont.ttf
new file mode 100755
index 0000000000..a1cc3c54d5
Binary files /dev/null and b/app/fonts/clearsans-medium-webfont.ttf differ
diff --git a/app/fonts/clearsans-mediumitalic-webfont.ttf b/app/fonts/clearsans-mediumitalic-webfont.ttf
new file mode 100755
index 0000000000..17f338c341
Binary files /dev/null and b/app/fonts/clearsans-mediumitalic-webfont.ttf differ
diff --git a/app/fonts/clearsans-regular-webfont.ttf b/app/fonts/clearsans-regular-webfont.ttf
new file mode 100755
index 0000000000..248778b300
Binary files /dev/null and b/app/fonts/clearsans-regular-webfont.ttf differ
diff --git a/app/fonts/clearsans-thin-webfont.ttf b/app/fonts/clearsans-thin-webfont.ttf
new file mode 100755
index 0000000000..69a6219cbc
Binary files /dev/null and b/app/fonts/clearsans-thin-webfont.ttf differ
diff --git a/app/fonts/streamline-24px.eot b/app/fonts/streamline-24px.eot
new file mode 100644
index 0000000000..9d4b7388f2
Binary files /dev/null and b/app/fonts/streamline-24px.eot differ
diff --git a/app/fonts/streamline-24px.svg b/app/fonts/streamline-24px.svg
new file mode 100644
index 0000000000..851c7def48
--- /dev/null
+++ b/app/fonts/streamline-24px.svg
@@ -0,0 +1,1652 @@
+
+
+
+Generated by Fontastic.me
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/fonts/streamline-24px.ttf b/app/fonts/streamline-24px.ttf
new file mode 100644
index 0000000000..7c68a974b3
Binary files /dev/null and b/app/fonts/streamline-24px.ttf differ
diff --git a/app/fonts/streamline-24px.woff b/app/fonts/streamline-24px.woff
new file mode 100644
index 0000000000..f816ee643b
Binary files /dev/null and b/app/fonts/streamline-24px.woff differ
diff --git a/app/images/loading.png b/app/images/loading.png
new file mode 100644
index 0000000000..6d8e975938
Binary files /dev/null and b/app/images/loading.png differ
diff --git a/app/images/loading@2x.png b/app/images/loading@2x.png
new file mode 100644
index 0000000000..87a14647c4
Binary files /dev/null and b/app/images/loading@2x.png differ
diff --git a/app/images/official.png b/app/images/official.png
new file mode 100644
index 0000000000..f96e0b73b4
Binary files /dev/null and b/app/images/official.png differ
diff --git a/app/images/official@2x.png b/app/images/official@2x.png
new file mode 100644
index 0000000000..1ba6879fc5
Binary files /dev/null and b/app/images/official@2x.png differ
diff --git a/app/images/restarting.png b/app/images/restarting.png
new file mode 100644
index 0000000000..64860c7b21
Binary files /dev/null and b/app/images/restarting.png differ
diff --git a/app/images/restarting@2x.png b/app/images/restarting@2x.png
new file mode 100644
index 0000000000..5e8a2f0953
Binary files /dev/null and b/app/images/restarting@2x.png differ
diff --git a/app/images/running.png b/app/images/running.png
new file mode 100644
index 0000000000..15ccd11b6b
Binary files /dev/null and b/app/images/running.png differ
diff --git a/app/images/running@2x.png b/app/images/running@2x.png
new file mode 100644
index 0000000000..406bfc05f4
Binary files /dev/null and b/app/images/running@2x.png differ
diff --git a/app/images/runningwave.png b/app/images/runningwave.png
new file mode 100644
index 0000000000..2934116bf3
Binary files /dev/null and b/app/images/runningwave.png differ
diff --git a/app/images/runningwave@2x.png b/app/images/runningwave@2x.png
new file mode 100644
index 0000000000..d20f942fce
Binary files /dev/null and b/app/images/runningwave@2x.png differ
diff --git a/app/index.html b/app/index.html
index 701d067f25..b33a677446 100644
--- a/app/index.html
+++ b/app/index.html
@@ -5,7 +5,6 @@
-