mirror of https://github.com/docker/docs.git
Image sizes & total progress. Restructure events
This commit is contained in:
parent
b2d319edcb
commit
183517b054
|
@ -19,6 +19,7 @@ var ContainerDetails = React.createClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentWillReceiveProps: function () {
|
componentWillReceiveProps: function () {
|
||||||
|
console.log('props');
|
||||||
this.update();
|
this.update();
|
||||||
var self = this;
|
var self = this;
|
||||||
var logs = [];
|
var logs = [];
|
||||||
|
@ -60,63 +61,25 @@ var ContainerDetails = React.createClass({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
componentWillMount: function () {
|
componentWillMount: function () {
|
||||||
this.update();
|
this.update();
|
||||||
var self = this;
|
|
||||||
var logs = [];
|
|
||||||
var index = 0;
|
|
||||||
docker.client().getContainer(this.getParams().name).logs({
|
|
||||||
follow: false,
|
|
||||||
stdout: true,
|
|
||||||
timestamps: true
|
|
||||||
}, function (err, stream) {
|
|
||||||
stream.setEncoding('utf8');
|
|
||||||
stream.on('data', function (buf) {
|
|
||||||
// Every other message is a header
|
|
||||||
if (index % 2 === 1) {
|
|
||||||
var time = buf.substr(0,buf.indexOf(' '));
|
|
||||||
var msg = buf.substr(buf.indexOf(' ')+1);
|
|
||||||
logs.push(convert.toHtml(self._escapeHTML(msg)));
|
|
||||||
}
|
|
||||||
index += 1;
|
|
||||||
});
|
|
||||||
stream.on('end', function (buf) {
|
|
||||||
self.setState({logs: logs});
|
|
||||||
docker.client().getContainer(self.getParams().name).logs({
|
|
||||||
follow: true,
|
|
||||||
stdout: true,
|
|
||||||
timestamps: true,
|
|
||||||
tail: 0
|
|
||||||
}, function (err, stream) {
|
|
||||||
stream.setEncoding('utf8');
|
|
||||||
stream.on('data', function (buf) {
|
|
||||||
// Every other message is a header
|
|
||||||
if (index % 2 === 1) {
|
|
||||||
var time = buf.substr(0,buf.indexOf(' '));
|
|
||||||
var msg = buf.substr(buf.indexOf(' ')+1);
|
|
||||||
logs.push(convert.toHtml(self._escapeHTML(msg)));
|
|
||||||
self.setState({logs: logs});
|
|
||||||
}
|
|
||||||
index += 1;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
componentDidMount: function () {
|
componentDidMount: function () {
|
||||||
var containerName = this.getParams().name;
|
ContainerStore.addChangeListener(ContainerStore.CONTAINERS, this.update);
|
||||||
ContainerStore.addChangeListener(containerName, this.update);
|
ContainerStore.addChangeListener(ContainerStore.PROGRESS, this.update);
|
||||||
},
|
},
|
||||||
componentWillUnmount: function () {
|
componentWillUnmount: function () {
|
||||||
var containerName = this.getParams().name;
|
ContainerStore.removeChangeListener(ContainerStore.CONTAINERS, this.update);
|
||||||
ContainerStore.removeChangeListener(containerName, this.update);
|
ContainerStore.removeChangeListener(ContainerStore.PROGRESS, this.update);
|
||||||
},
|
},
|
||||||
update: function () {
|
update: function () {
|
||||||
var containerName = this.getParams().name;
|
var name = this.getParams().name;
|
||||||
|
var container = ContainerStore.container(name);
|
||||||
|
var progress = ContainerStore.progress(name);
|
||||||
this.setState({
|
this.setState({
|
||||||
container: ContainerStore.containers()[containerName]
|
progress: progress,
|
||||||
|
container: container
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
_escapeHTML: function (html) {
|
_escapeHTML: function (html) {
|
||||||
|
@ -137,6 +100,10 @@ var ContainerDetails = React.createClass({
|
||||||
return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>;
|
return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!this.state.container) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var state;
|
var state;
|
||||||
if (this.state.container.State.Running) {
|
if (this.state.container.State.Running) {
|
||||||
state = <h2 className="status">running</h2>;
|
state = <h2 className="status">running</h2>;
|
||||||
|
@ -148,6 +115,7 @@ var ContainerDetails = React.createClass({
|
||||||
<div className="details">
|
<div className="details">
|
||||||
<div className="details-header">
|
<div className="details-header">
|
||||||
<h1>{this.state.container.Name.replace('/', '')}</h1>
|
<h1>{this.state.container.Name.replace('/', '')}</h1>
|
||||||
|
<h2>{this.state.progress}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="details-logs">
|
<div className="details-logs">
|
||||||
<div className="logs">
|
<div className="logs">
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
var async = require('async');
|
||||||
|
var _ = require('underscore');
|
||||||
|
var $ = require('jquery');
|
||||||
var React = require('react/addons');
|
var React = require('react/addons');
|
||||||
var Router = require('react-router');
|
var Router = require('react-router');
|
||||||
var Modal = require('react-bootstrap/Modal');
|
var Modal = require('react-bootstrap/Modal');
|
||||||
|
@ -6,10 +9,7 @@ var ModalTrigger = require('react-bootstrap/ModalTrigger');
|
||||||
var ContainerModal = require('./ContainerModal.react');
|
var ContainerModal = require('./ContainerModal.react');
|
||||||
var ContainerStore = require('./ContainerStore');
|
var ContainerStore = require('./ContainerStore');
|
||||||
var Header = require('./Header.react');
|
var Header = require('./Header.react');
|
||||||
var async = require('async');
|
|
||||||
var _ = require('underscore');
|
|
||||||
var docker = require('./docker');
|
var docker = require('./docker');
|
||||||
var $ = require('jquery');
|
|
||||||
|
|
||||||
var Link = Router.Link;
|
var Link = Router.Link;
|
||||||
var RouteHandler = Router.RouteHandler;
|
var RouteHandler = Router.RouteHandler;
|
||||||
|
@ -24,37 +24,34 @@ var ContainerList = React.createClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentDidMount: function () {
|
componentDidMount: function () {
|
||||||
this.update();
|
this.updateContainers();
|
||||||
if (this.state.containers.length > 0) {
|
ContainerStore.addChangeListener(ContainerStore.ACTIVE, this.updateActive);
|
||||||
var name = this.state.containers[0].Name.replace('/', '');
|
ContainerStore.addChangeListener(ContainerStore.CONTAINERS, this.updateContainers);
|
||||||
active = name;
|
|
||||||
ContainerStore.setActive(name);
|
|
||||||
}
|
|
||||||
ContainerStore.addChangeListener(ContainerStore.CONTAINERS, this.update);
|
|
||||||
ContainerStore.addChangeListener(ContainerStore.ACTIVE, this.update);
|
|
||||||
},
|
},
|
||||||
componentWillMount: function () {
|
componentWillMount: function () {
|
||||||
this._start = Date.now();
|
this._start = Date.now();
|
||||||
},
|
},
|
||||||
componentWillUnmount: function () {
|
componentWillUnmount: function () {
|
||||||
ContainerStore.removeChangeListener(ContainerStore.CONTAINERS, this.update);
|
ContainerStore.removeChangeListener(ContainerStore.CONTAINERS, this.updateContainers);
|
||||||
ContainerStore.removeChangeListener(ContainerStore.ACTIVE, this.update);
|
ContainerStore.removeChangeListener(ContainerStore.ACTIVE, updateActive.update);
|
||||||
},
|
},
|
||||||
componentDidUpdate: function () {
|
updateActive: function () {
|
||||||
|
if (ContainerStore.active()) {
|
||||||
|
this.transitionTo('container', {name: ContainerStore.active()});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
update: function () {
|
updateContainers: function () {
|
||||||
|
// Sort by name
|
||||||
var containers = _.values(ContainerStore.containers()).sort(function (a, b) {
|
var containers = _.values(ContainerStore.containers()).sort(function (a, b) {
|
||||||
return a.Name.localeCompare(b.Name);
|
return a.Name.localeCompare(b.Name);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({containers: containers});
|
||||||
active: ContainerStore.active(),
|
|
||||||
containers: containers
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ContainerStore.active()) {
|
// Transition to the active container or set one
|
||||||
this.transitionTo('container', {name: ContainerStore.active()});
|
var active = ContainerStore.active();
|
||||||
|
if (!ContainerStore.container(active) && containers.length > 0) {
|
||||||
|
ContainerStore.setActive(containers[0].Name.replace('/', ''));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleClick: function (containerId) {
|
handleClick: function (containerId) {
|
||||||
|
@ -77,13 +74,12 @@ var ContainerList = React.createClass({
|
||||||
|
|
||||||
var imageName = downloadingImage || container.Config.Image;
|
var imageName = downloadingImage || container.Config.Image;
|
||||||
|
|
||||||
var state;
|
|
||||||
|
|
||||||
// Synchronize all animations
|
// Synchronize all animations
|
||||||
var style = {
|
var style = {
|
||||||
WebkitAnimationDelay: (self._start - Date.now()) + 'ms'
|
WebkitAnimationDelay: (self._start - Date.now()) + 'ms'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var state;
|
||||||
if (downloading) {
|
if (downloading) {
|
||||||
state = <div className="state state-downloading"><div style={style} className="downloading-arrow"></div></div>;
|
state = <div className="state state-downloading"><div style={style} className="downloading-arrow"></div></div>;
|
||||||
} else if (container.State.Running && !container.State.Paused) {
|
} else if (container.State.Running && !container.State.Paused) {
|
||||||
|
@ -123,52 +119,4 @@ var ContainerList = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var Containers = React.createClass({
|
module.exports = ContainerList;
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
sidebarOffset: 0
|
|
||||||
};
|
|
||||||
},
|
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var sidebarHeaderClass = 'sidebar-header';
|
|
||||||
if (this.state.sidebarOffset) {
|
|
||||||
sidebarHeaderClass += ' sep';
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="containers">
|
|
||||||
<Header/>
|
|
||||||
<div className="containers-body">
|
|
||||||
<div className="sidebar">
|
|
||||||
<section className={sidebarHeaderClass}>
|
|
||||||
<h3>containers</h3>
|
|
||||||
<div className="create">
|
|
||||||
<ModalTrigger modal={<ContainerModal/>}>
|
|
||||||
<div className="wrapper">
|
|
||||||
<span className="icon icon-add-3"></span>
|
|
||||||
</div>
|
|
||||||
</ModalTrigger>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section className="sidebar-containers" onScroll={this.handleScroll}>
|
|
||||||
<ContainerList/>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<RouteHandler/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Containers;
|
|
|
@ -8,6 +8,7 @@ var ContainerStore = require('./ContainerStore');
|
||||||
var Navigation = Router.Navigation;
|
var Navigation = Router.Navigation;
|
||||||
|
|
||||||
var ContainerModal = React.createClass({
|
var ContainerModal = React.createClass({
|
||||||
|
_searchRequest: null,
|
||||||
getInitialState: function () {
|
getInitialState: function () {
|
||||||
return {
|
return {
|
||||||
query: '',
|
query: '',
|
||||||
|
@ -19,9 +20,11 @@ var ContainerModal = React.createClass({
|
||||||
},
|
},
|
||||||
search: function (query) {
|
search: function (query) {
|
||||||
var self = this;
|
var self = this;
|
||||||
$.get('https://registry.hub.docker.com/v1/search?q=' + query, function (result) {
|
this._searchRequest = $.get('https://registry.hub.docker.com/v1/search?q=' + query, function (result) {
|
||||||
self.setState(result);
|
if (self.isMounted()) {
|
||||||
console.log(result);
|
self.setState(result);
|
||||||
|
console.log(result);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleChange: function (e) {
|
handleChange: function (e) {
|
||||||
|
@ -30,6 +33,11 @@ var ContainerModal = React.createClass({
|
||||||
if (query === this.state.query) {
|
if (query === this.state.query) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._searchRequest) {
|
||||||
|
this._searchRequest.abort();
|
||||||
|
this._searchRequest = null;
|
||||||
|
}
|
||||||
clearTimeout(this.timeout);
|
clearTimeout(this.timeout);
|
||||||
var self = this;
|
var self = this;
|
||||||
this.timeout = setTimeout(function () {
|
this.timeout = setTimeout(function () {
|
||||||
|
|
|
@ -1,89 +1,21 @@
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var assign = require('react/lib/Object.assign');
|
var assign = require('react/lib/Object.assign');
|
||||||
var docker = require('./docker.js');
|
var docker = require('./docker');
|
||||||
|
var registry = require('./registry');
|
||||||
var $ = require('jquery');
|
var $ = require('jquery');
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
|
|
||||||
// Merge our store with Node's Event Emitter
|
// Merge our store with Node's Event Emitter
|
||||||
var ContainerStore = assign(EventEmitter.prototype, {
|
var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
CONTAINERS: 'containers',
|
CONTAINERS: 'containers',
|
||||||
|
PROGRESS: 'progress',
|
||||||
|
LOGS: 'logs',
|
||||||
ACTIVE: 'active',
|
ACTIVE: 'active',
|
||||||
_containers: {},
|
_containers: {},
|
||||||
|
_progress: {},
|
||||||
_logs: {},
|
_logs: {},
|
||||||
_active: null,
|
_active: null,
|
||||||
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) {
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
// TODO: Dont refresh on deleting placeholder containers
|
|
||||||
self.update(function (err) {
|
|
||||||
console.log('Updated container data.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
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.forEach(function (r) {
|
|
||||||
containers[r.Name.replace('/', '')] = r;
|
|
||||||
});
|
|
||||||
self._containers = containers;
|
|
||||||
_.keys(self._containers).forEach(function(c) {
|
|
||||||
self.emit(c);
|
|
||||||
});
|
|
||||||
self.emit(self.CONTAINERS);
|
|
||||||
callback(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
_pullScratchImage: function (callback) {
|
_pullScratchImage: function (callback) {
|
||||||
var image = docker.client().getImage('scratch:latest');
|
var image = docker.client().getImage('scratch:latest');
|
||||||
image.inspect(function (err, data) {
|
image.inspect(function (err, data) {
|
||||||
|
@ -163,41 +95,156 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Returns all containers
|
init: function (callback) {
|
||||||
containers: function() {
|
// TODO: Load cached data from db on loading
|
||||||
return this._containers;
|
|
||||||
|
// 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 () {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
docker.client().getEvents(function (err, stream) {
|
||||||
|
stream.setEncoding('utf8');
|
||||||
|
stream.on('data', function (data) {
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
// TODO: Dont refresh on deleting placeholder containers
|
||||||
|
var deletingPlaceholder = data.status === 'destroy' && self.container(data.id) && self.container(data.id).Config.Env.indexOf('KITEMATIC_DOWNLOADING=true') !== -1;
|
||||||
|
console.log(deletingPlaceholder);
|
||||||
|
if (!deletingPlaceholder) {
|
||||||
|
self.update(function (err) {
|
||||||
|
console.log('Updated container data.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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.forEach(function (r) {
|
||||||
|
containers[r.Name.replace('/', '')] = r;
|
||||||
|
});
|
||||||
|
self._containers = containers;
|
||||||
|
self.emit(self.CONTAINERS);
|
||||||
|
callback(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
create: function (repository, tag, callback) {
|
create: function (repository, tag, callback) {
|
||||||
var containerName = this._generateName(repository);
|
|
||||||
tag = tag || 'latest';
|
tag = tag || 'latest';
|
||||||
var imageName = repository + ':' + tag;
|
|
||||||
// Check if image is not local or already being downloaded
|
|
||||||
console.log('Creating container.');
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var imageName = repository + ':' + tag;
|
||||||
|
var containerName = this._generateName(repository);
|
||||||
var image = docker.client().getImage(imageName);
|
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) {
|
|
||||||
|
|
||||||
});*/
|
image.inspect(function (err, data) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
// Pull image
|
// Pull image
|
||||||
self._createPlaceholderContainer(imageName, containerName, function (err, container) {
|
self._createPlaceholderContainer(imageName, containerName, function (err, container) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
console.log('Placeholder container created.');
|
registry.layers(repository, tag, function (err, layerSizes) {
|
||||||
docker.client().pull(imageName, function (err, stream) {
|
if (err) {
|
||||||
console.log(containerName);
|
callback(err);
|
||||||
callback(null, containerName);
|
}
|
||||||
stream.setEncoding('utf8');
|
|
||||||
stream.on('data', function (data) {
|
// TODO: Support v2 registry API
|
||||||
// TODO: update progress
|
// TODO: clean this up- It's messy to work with pulls from both the v1 and v2 registry APIs
|
||||||
//console.log(data);
|
// Use the per-layer pull progress % to update the total progress.
|
||||||
});
|
docker.client().listImages({all: 1}, function(err, images) {
|
||||||
stream.on('end', function () {
|
var existingIds = new Set(images.map(function (image) {
|
||||||
self._createContainer(imageName, containerName, function () {
|
return image.Id.slice(0, 12);
|
||||||
|
}));
|
||||||
|
var layersToDownload = layerSizes.filter(function (layerSize) {
|
||||||
|
return !existingIds.has(layerSize.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
var totalBytes = layersToDownload.map(function (s) { return s.size; }).reduce(function (pv, sv) { return pv + sv; }, 0);
|
||||||
|
docker.client().pull(imageName, function (err, stream) {
|
||||||
|
callback(null, containerName);
|
||||||
|
stream.setEncoding('utf8');
|
||||||
|
|
||||||
|
var layerProgress = layersToDownload.reduce(function (r, layer) {
|
||||||
|
if (_.findWhere(images, {Id: layer.Id})) {
|
||||||
|
r[layer.Id] = 100;
|
||||||
|
} else {
|
||||||
|
r[layer.Id] = 0;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
self._progress[containerName] = 0;
|
||||||
|
self.emit(containerName);
|
||||||
|
|
||||||
|
stream.on('data', function (str) {
|
||||||
|
console.log(str);
|
||||||
|
var data = JSON.parse(str);
|
||||||
|
|
||||||
|
if (data.status === 'Already exists') {
|
||||||
|
layerProgress[data.id] = 1;
|
||||||
|
} else if (data.status === 'Downloading') {
|
||||||
|
var current = data.progressDetail.current;
|
||||||
|
var total = data.progressDetail.total;
|
||||||
|
var layerFraction = current / total;
|
||||||
|
layerProgress[data.id] = layerFraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunks = layersToDownload.map(function (s) {
|
||||||
|
return layerProgress[s.Id] * s.size;
|
||||||
|
});
|
||||||
|
|
||||||
|
var totalReceived = chunks.reduce(function (pv, sv) {
|
||||||
|
return pv + sv;
|
||||||
|
});
|
||||||
|
|
||||||
|
var totalProgress = totalReceived / totalBytes;
|
||||||
|
self._progress[containerName] = totalProgress;
|
||||||
|
self.emit(self.PROGRESS);
|
||||||
|
});
|
||||||
|
stream.on('end', function () {
|
||||||
|
self._createContainer(imageName, containerName, function () {
|
||||||
|
delete self._progress[containerName];
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -210,19 +257,27 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// If so then create a container w/ kitematic-only 'downloading state'
|
|
||||||
// Pull image
|
|
||||||
// When image is done pulling then
|
|
||||||
},
|
},
|
||||||
setActive: function (containerName) {
|
setActive: function (name) {
|
||||||
this._active = containerName;
|
console.log('set active');
|
||||||
this.emit(self.ACTIVE);
|
this._active = name;
|
||||||
|
this.emit(this.ACTIVE);
|
||||||
},
|
},
|
||||||
active: function () {
|
active: function () {
|
||||||
return this._active;
|
return this._active;
|
||||||
},
|
},
|
||||||
logs: function (containerName) {
|
// Returns all containers
|
||||||
return logs[containerId];
|
containers: function() {
|
||||||
|
return this._containers;
|
||||||
|
},
|
||||||
|
container: function (name) {
|
||||||
|
return this._containers[name];
|
||||||
|
},
|
||||||
|
progress: function (name) {
|
||||||
|
return this._progress[name];
|
||||||
|
},
|
||||||
|
logs: function (name) {
|
||||||
|
return logs[name];
|
||||||
},
|
},
|
||||||
addChangeListener: function(eventType, callback) {
|
addChangeListener: function(eventType, callback) {
|
||||||
this.on(eventType, callback);
|
this.on(eventType, callback);
|
||||||
|
|
20
app/main.js
20
app/main.js
|
@ -8,8 +8,8 @@ var boot2docker = require('./boot2docker.js');
|
||||||
var Setup = require('./Setup.react');
|
var Setup = require('./Setup.react');
|
||||||
var Containers = require('./Containers.react');
|
var Containers = require('./Containers.react');
|
||||||
var ContainerDetails = require('./ContainerDetails.react');
|
var ContainerDetails = require('./ContainerDetails.react');
|
||||||
var ContainerStore = require('./ContainerStore.js');
|
var ContainerStore = require('./ContainerStore');
|
||||||
var Radial = require('./Radial.react.js');
|
var Radial = require('./Radial.react');
|
||||||
|
|
||||||
var Route = Router.Route;
|
var Route = Router.Route;
|
||||||
var NotFoundRoute = Router.NotFoundRoute;
|
var NotFoundRoute = Router.NotFoundRoute;
|
||||||
|
@ -17,6 +17,14 @@ var DefaultRoute = Router.DefaultRoute;
|
||||||
var Link = Router.Link;
|
var Link = Router.Link;
|
||||||
var RouteHandler = Router.RouteHandler;
|
var RouteHandler = Router.RouteHandler;
|
||||||
|
|
||||||
|
var App = React.createClass({
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<RouteHandler/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var NoContainers = React.createClass({
|
var NoContainers = React.createClass({
|
||||||
render: function () {
|
render: function () {
|
||||||
return (
|
return (
|
||||||
|
@ -27,14 +35,6 @@ var NoContainers = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var App = React.createClass({
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<RouteHandler/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var routes = (
|
var routes = (
|
||||||
<Route name="app" path="/" handler={App}>
|
<Route name="app" path="/" handler={App}>
|
||||||
<Route name="containers" handler={Containers}>
|
<Route name="containers" handler={Containers}>
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
var async = require('async');
|
||||||
|
var $ = require('jquery');
|
||||||
|
|
||||||
|
var Registry = {
|
||||||
|
token: function(repository, callback) {
|
||||||
|
$.ajax({
|
||||||
|
url: 'https://registry.hub.docker.com/v1/repositories/' + repository + '/images',
|
||||||
|
headers: {
|
||||||
|
'X-Docker-Token': true,
|
||||||
|
},
|
||||||
|
success: function (res, status, xhr) {
|
||||||
|
callback(null, xhr.getResponseHeader('X-Docker-Token'));
|
||||||
|
},
|
||||||
|
error: function (err) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
ancestry: function (imageId, token, callback) {
|
||||||
|
$.ajax({
|
||||||
|
url: 'https://registry-1.docker.io/v1/images/' + imageId + '/ancestry',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Token ' + token
|
||||||
|
},
|
||||||
|
success: function (layers, status, xhr) {
|
||||||
|
callback(null, layers);
|
||||||
|
},
|
||||||
|
error: function (err) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
imageId: function (repository, tag, token, callback) {
|
||||||
|
$.ajax({
|
||||||
|
url: 'https://registry-1.docker.io/v1/repositories/' + repository + '/tags/' + tag,
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Token ' + token
|
||||||
|
},
|
||||||
|
success: function (res, status, xhr) {
|
||||||
|
callback(null, res);
|
||||||
|
},
|
||||||
|
error: function (err) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Returns an array [{Id: <12 character image ID, size: size of layer in bytes}]
|
||||||
|
layers: function (repository, tag, callback) {
|
||||||
|
var self = this;
|
||||||
|
this.token(repository, function (err, token) {
|
||||||
|
self.imageId(repository, tag, token, function (err, imageId) {
|
||||||
|
self.ancestry(imageId, token, function (err, layers) {
|
||||||
|
async.map(layers, function (layer, callback) {
|
||||||
|
$.ajax({
|
||||||
|
url: 'https://registry-1.docker.io/v1/images/' + layer + '/json',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Token ' + token
|
||||||
|
},
|
||||||
|
success: function (res, status, xhr) {
|
||||||
|
var size = xhr.getResponseHeader('X-Docker-Size');
|
||||||
|
callback(null, {
|
||||||
|
Id: layer.slice(0, 12),
|
||||||
|
size: parseInt(size, 10)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function (err) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, function (err, results) {
|
||||||
|
if (err) {
|
||||||
|
callback('Could not sum' + err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(null, results);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Registry;
|
Loading…
Reference in New Issue