diff --git a/app/ContainerDetails.react.js b/app/ContainerDetails.react.js index 86c66974dc..63c0e733c8 100644 --- a/app/ContainerDetails.react.js +++ b/app/ContainerDetails.react.js @@ -13,25 +13,12 @@ var docker = require('./docker.js'); var ContainerDetails = React.createClass({ mixins: [Router.State], - componentDidMount: function () { - ContainerStore.addChangeListener(this.update); + getInitialState: function () { + return { + logs: [] + }; }, - 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 () { + componentWillMount: function () { this.update(); var self = this; var logs = []; @@ -74,15 +61,27 @@ var ContainerDetails = React.createClass({ }); }); }, + 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; + }, render: function () { var self = this; - if (!this.state || !this.state.logs) { - return false; - } - - // console.log(container); - if (!this.state) { return
; } diff --git a/app/ContainerStore.js b/app/ContainerStore.js index 973247bf17..3c0dc93ed9 100644 --- a/app/ContainerStore.js +++ b/app/ContainerStore.js @@ -3,22 +3,49 @@ var async = require('async'); var assign = require('react/lib/Object.assign'); var docker = require('./docker.js'); var $ = require('jquery'); +var _ = require('underscore'); // 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 + _logs: {}, + init: function (callback) { + // TODO: Load cached data from db on loading // Refresh with docker & hook into events var self = this; this.update(function (err) { + callback(); + var downloading = _.filter(_.values(self._containers), function (container) { + var env = container.Config.Env; + return _.indexOf(env, 'KITEMATIC_DOWNLOADING=true') !== -1; + }); + + // Recover any pulls that were happening + downloading.forEach(function (container) { + var env = _.object(container.Config.Env.map(function (e) { + return e.split('='); + })); + docker.client().pull(env.KITEMATIC_DOWNLOADING_IMAGE, function (err, stream) { + stream.setEncoding('utf8'); + stream.on('data', function (data) { + console.log(data); + }); + stream.on('end', function () { + self._createContainer(env.KITEMATIC_DOWNLOADING_IMAGE, container.Name.replace('/', ''), function () { + console.log('RECOVERED'); + }); + }); + }); + }); + docker.client().getEvents(function (err, stream) { stream.setEncoding('utf8'); stream.on('data', function (data) { - self.update(function (err) { + // TODO: Make + self.update(function (err) { + console.log('Updated container data.'); }); }); }); @@ -45,57 +72,133 @@ var ContainerStore = assign(EventEmitter.prototype, { containers[r.Id] = r; }); self._containers = containers; + console.log(containers); self.emit('change'); callback(null); }); }); }, - _createContainer: function (image) { - docker.client().createContainer({ - Image: image, - Tty: false - }, function (err, container) { + _pullScratchImage: function (callback) { + var image = docker.client().getImage('scratch:latest'); + image.inspect(function (err, data) { if (err) { - callback(err, null); + callback(err); 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); + if (!data) { + docker.client().pull('scratch:latest', function (err, stream) { + if (err) { + callback(err); + return; + } + stream.setEncoding('utf8'); + stream.on('data', function (data) {}); + stream.on('end', function () { + callback(); + }); + }); + } else { + callback(); + } + }); + }, + _createContainer: function (image, name, callback) { + var existing = docker.client().getContainer(name); + existing.remove(function (err, data) { + console.log('Placeholder removed.'); + docker.client().createContainer({ + Image: image, + Tty: false, + name: name + }, 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); + }); }); }); }, + _createPlaceholderContainer: function (imageName, name, callback) { + console.log('_createPlaceholderContainer', imageName, name); + this._pullScratchImage(function (err) { + docker.client().createContainer({ + Image: 'scratch:latest', + Tty: false, + Env: [ + 'KITEMATIC_DOWNLOADING=true', + 'KITEMATIC_DOWNLOADING_IMAGE=' + imageName + ], + Cmd: 'placeholder', + name: name + }, function (err, container) { + callback(err, container); + }); + }); + }, + _generateName: function (repository) { + var base = _.last(repository.split('/')); + var count = 1; + var name = base; + while (true) { + var exists = _.findWhere(_.values(this._containers), {Name: '/' + name}) || _.findWhere(_.values(this._containers), {Name: name}); + if (!exists) { + return name; + } else { + count++; + name = base + '-' + count; + } + } + }, create: function (repository, tag, callback) { + + console.log('create', repository, tag); + + var containerName = this._generateName(repository); tag = tag || 'latest'; - var name = repository + ':' + tag; + var imageName = 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); + var image = docker.client().getImage(imageName); + console.log(image); image.inspect(function (err, data) { + // TODO: Get image size from registry API /*$.get('https://registry.hub.docker.com/v1/repositories/' + repository + '/tags/' + tag, function (data) { });*/ - if (data === null) { + if (!data) { // 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); + self._createPlaceholderContainer(imageName, containerName, function (err, container) { + if (err) { + console.log(err); + } + console.log('Placeholder container created.'); + docker.client().pull(imageName, function (err, stream) { + stream.setEncoding('utf8'); + stream.on('data', function (data) { + console.log(data); + }); + stream.on('end', function () { + self._createContainer(imageName, containerName, function () { + }); + }); }); }); // Create placeholder container } else { // If not then directly create the container - self._createContainer(name); + self._createContainer(imageName, containerName, function () { + console.log('done'); + }); } }); // If so then create a container w/ kitematic-only 'downloading state' diff --git a/app/Containers.react.js b/app/Containers.react.js index c1c81e5274..c9a562e43e 100644 --- a/app/Containers.react.js +++ b/app/Containers.react.js @@ -24,10 +24,17 @@ var ContainerList = React.createClass({ }; }, handleClick: function () { - console.log('hi'); + }, componentDidMount: function () { + this.update(); ContainerStore.addChangeListener(this.update); + if (this.state.containers.length > 0) { + this.transitionTo('container', {Id: this.state.containers[0].Id}); + } + }, + componentWillMount: function () { + this._start = Date.now(); }, componentWillUnmount: function () { ContainerStore.removeChangeListener(this.update); @@ -36,24 +43,50 @@ var ContainerList = React.createClass({ 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 self = this; var containers = this.state.containers.map(function (container) { + var downloadingImage = null, downloading = false; + var env = container.Config.Env; + if (env.length) { + var obj = _.object(env.map(function (e) { + return e.split('='); + })); + if (obj.KITEMATIC_DOWNLOADING) { + downloading = true; + } + downloadingImage = obj.KITEMATIC_DOWNLOADING_IMAGE || null; + } + + var imageName = downloadingImage || container.Config.Image; + var state; - if (container.State.Running) { - state =