From 7e74481e85b3fac7624734f9fb9f7b7d58201b42 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sat, 17 Jan 2015 13:15:26 -0500 Subject: [PATCH] Pull & Run containers --- ...ner.react.js => ContainerDetails.react.js} | 49 +- app/ContainerModal.react.js | 93 + app/ContainerStore.js | 120 + app/Containers.react.js | 129 +- app/Header.react.js | 49 +- app/boot2docker.js | 2 +- app/docker.js | 10 +- app/fonts/clearsans-bold-webfont.ttf | Bin 0 -> 66980 bytes app/fonts/clearsans-bolditalic-webfont.ttf | Bin 0 -> 78556 bytes app/fonts/clearsans-italic-webfont.ttf | Bin 0 -> 80932 bytes app/fonts/clearsans-light-webfont.ttf | Bin 0 -> 69280 bytes app/fonts/clearsans-medium-webfont.ttf | Bin 0 -> 70916 bytes app/fonts/clearsans-mediumitalic-webfont.ttf | Bin 0 -> 81404 bytes app/fonts/clearsans-regular-webfont.ttf | Bin 0 -> 67256 bytes app/fonts/clearsans-thin-webfont.ttf | Bin 0 -> 68780 bytes app/fonts/streamline-24px.eot | Bin 0 -> 434862 bytes app/fonts/streamline-24px.svg | 1652 ++++++ app/fonts/streamline-24px.ttf | Bin 0 -> 434672 bytes app/fonts/streamline-24px.woff | Bin 0 -> 208040 bytes app/images/loading.png | Bin 0 -> 807 bytes app/images/loading@2x.png | Bin 0 -> 1703 bytes app/images/official.png | Bin 0 -> 609 bytes app/images/official@2x.png | Bin 0 -> 1391 bytes app/images/restarting.png | Bin 0 -> 641 bytes app/images/restarting@2x.png | Bin 0 -> 1368 bytes app/images/running.png | Bin 0 -> 536 bytes app/images/running@2x.png | Bin 0 -> 1203 bytes app/images/runningwave.png | Bin 0 -> 355 bytes app/images/runningwave@2x.png | Bin 0 -> 654 bytes app/index.html | 1 - app/main.js | 9 +- app/styles/bootstrap/variables.less | 2 +- app/styles/icons.less | 4966 +++++++++++++++++ app/styles/main.less | 532 +- app/styles/theme.less | 76 + app/styles/variables.less | 3 +- browser/main.js | 4 +- gulpfile.js | 2 +- kitematic.icns | Bin 560672 -> 1187495 bytes package.json | 14 +- 40 files changed, 7505 insertions(+), 208 deletions(-) rename app/{Container.react.js => ContainerDetails.react.js} (66%) create mode 100644 app/ContainerModal.react.js create mode 100644 app/ContainerStore.js create mode 100755 app/fonts/clearsans-bold-webfont.ttf create mode 100755 app/fonts/clearsans-bolditalic-webfont.ttf create mode 100755 app/fonts/clearsans-italic-webfont.ttf create mode 100755 app/fonts/clearsans-light-webfont.ttf create mode 100755 app/fonts/clearsans-medium-webfont.ttf create mode 100755 app/fonts/clearsans-mediumitalic-webfont.ttf create mode 100755 app/fonts/clearsans-regular-webfont.ttf create mode 100755 app/fonts/clearsans-thin-webfont.ttf create mode 100644 app/fonts/streamline-24px.eot create mode 100644 app/fonts/streamline-24px.svg create mode 100644 app/fonts/streamline-24px.ttf create mode 100644 app/fonts/streamline-24px.woff create mode 100644 app/images/loading.png create mode 100644 app/images/loading@2x.png create mode 100644 app/images/official.png create mode 100644 app/images/official@2x.png create mode 100644 app/images/restarting.png create mode 100644 app/images/restarting@2x.png create mode 100644 app/images/running.png create mode 100644 app/images/running@2x.png create mode 100644 app/images/runningwave.png create mode 100644 app/images/runningwave@2x.png create mode 100644 app/styles/icons.less create mode 100644 app/styles/theme.less diff --git a/app/Container.react.js b/app/ContainerDetails.react.js similarity index 66% rename from app/Container.react.js rename to app/ContainerDetails.react.js index 8369d64938..86c66974dc 100644 --- a/app/Container.react.js +++ b/app/ContainerDetails.react.js @@ -6,15 +6,33 @@ var NotFoundRoute = Router.NotFoundRoute; var DefaultRoute = Router.DefaultRoute; var Link = Router.Link; var RouteHandler = Router.RouteHandler; - var Convert = require('ansi-to-html'); var convert = new Convert(); - +var ContainerStore = require('./ContainerStore.js'); var docker = require('./docker.js'); -var Container = React.createClass({ +var ContainerDetails = React.createClass({ mixins: [Router.State], + componentDidMount: function () { + ContainerStore.addChangeListener(this.update); + }, + componentWillUnmount: function () { + ContainerStore.removeChangeListener(this.update); + }, + update: function () { + var containerId = this.getParams().Id; + this.setState({ + container: ContainerStore.containers()[containerId] + }); + }, + _escapeHTML: function (html) { + var text = document.createTextNode(html); + var div = document.createElement('div'); + div.appendChild(text); + return div.innerHTML; + }, componentWillReceiveProps: function () { + this.update(); var self = this; var logs = []; var index = 0; @@ -29,7 +47,7 @@ var Container = React.createClass({ if (index % 2 === 1) { var time = buf.substr(0,buf.indexOf(' ')); var msg = buf.substr(buf.indexOf(' ')+1); - logs.push(convert.toHtml(msg)); + logs.push(convert.toHtml(self._escapeHTML(msg))); } index += 1; }); @@ -47,7 +65,7 @@ var Container = React.createClass({ if (index % 2 === 1) { var time = buf.substr(0,buf.indexOf(' ')); var msg = buf.substr(buf.indexOf(' ')+1); - logs.push(convert.toHtml(msg)); + logs.push(convert.toHtml(self._escapeHTML(msg))); self.setState({logs: logs}); } index += 1; @@ -63,12 +81,9 @@ var Container = React.createClass({ return false; } - var container = _.find(this.props.containers, function (container) { - return container.Id === self.getParams().Id; - }); // console.log(container); - if (!container || !this.state) { + if (!this.state) { return
; } @@ -77,23 +92,25 @@ var Container = React.createClass({ }); var state; - if (container.State.Running) { + if (this.state.container.State.Running) { state =

running

; - } else if (container.State.Restarting) { + } else if (this.state.container.State.Restarting) { state =

restarting

; } return ( -
+
-

{container.Name.replace('/', '')}

{state} +

{this.state.container.Name.replace('/', '')}

-
- {logs} +
+
+ {logs} +
); } }); -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 ( +
  • +
    +
    + {name} +
    +
    +
    +
    {r.star_count}
    +
    +
    +
    + +
    +
  • + ); + }); + return ( + +
    +
    + + +
    +
    Results
    +
      + {results} +
    +
    +
    + +
    +
    + ); + } +}); + +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 ( -