mirror of https://github.com/docker/docs.git
Merge branch 'master' into hub
This commit is contained in:
commit
2400b17c5a
|
@ -18,8 +18,7 @@ Before you fil an issue or a pull request, quickly read of the following tips on
|
|||
### Prerequisites
|
||||
|
||||
Most of the time, you'll have installed Kitematic before contibuting, but for the
|
||||
sake of completeness, you can also install [Node.js](https://nodejs.org/) and then
|
||||
run from your Git clone.
|
||||
sake of completeness, you can also install [Node.js](https://nodejs.org/) and the latest Xcode from the Apple App Store and then run from your Git clone.
|
||||
|
||||
Running `npm start` will download and install the OS X Docker client,
|
||||
[Docker machine](https://github.com/docker/machine),
|
||||
|
|
|
@ -15,6 +15,12 @@ Kitematic is a simple application for managing Docker containers on Mac OS X and
|
|||
|
||||
Kitematic's documentation and other information can be found at [http://kitematic.com/docs](http://kitematic.com/docs).
|
||||
|
||||
## Security Disclosure
|
||||
|
||||
Security is very important to us. If you have any issue regarding security,
|
||||
please disclose the information responsibly by sending an email to
|
||||
security@docker.com and not by creating a github issue.
|
||||
|
||||
## Bugs and Feature Requests
|
||||
|
||||
Have a bug or a feature request? Please first read the [Issue Guidelines](https://github.com/kitematic/kitematic/blob/master/CONTRIBUTING.md#using-the-issue-tracker) and search for existing and closed issues. If your problem or idea is not addressed yet, [please open a new issue](https://github.com/kitematic/kitematic/issues/new).
|
||||
|
@ -45,3 +51,4 @@ rm -rf ~/Library/Application\ Support/Kitematic
|
|||
## Copyright and License
|
||||
|
||||
Code released under the [Apache license](LICENSE).
|
||||
Images are copyrighted by Docker, Inc.
|
||||
|
|
|
@ -2,6 +2,7 @@ var _ = require('underscore');
|
|||
var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var Radial = require('./Radial.react');
|
||||
var ContainerProgress = require('./ContainerProgress.react');
|
||||
var ContainerHomePreview = require('./ContainerHomePreview.react');
|
||||
var ContainerHomeLogs = require('./ContainerHomeLogs.react');
|
||||
var ContainerHomeFolders = require('./ContainerHomeFolders.react');
|
||||
|
@ -50,23 +51,29 @@ var ContainerHome = React.createClass({
|
|||
</div>
|
||||
);
|
||||
} else if (this.props.container && this.props.container.State.Downloading) {
|
||||
if (this.props.container.Progress !== undefined) {
|
||||
if (this.props.container.Progress > 0) {
|
||||
body = (
|
||||
<div className="details-progress">
|
||||
<h2>Downloading Image</h2>
|
||||
<Radial progress={Math.min(Math.round(this.props.container.Progress), 99)} thick={true} gray={true}/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
body = (
|
||||
<div className="details-progress">
|
||||
<h2>Downloading Image</h2>
|
||||
<Radial spin="true" progress="90" thick={true} transparent={true}/>
|
||||
</div>
|
||||
);
|
||||
if (this.props.container.Progress) {
|
||||
|
||||
let fields = [];
|
||||
let values = [];
|
||||
let sum = 0.0;
|
||||
|
||||
for (let i = 0; i < this.props.container.Progress.amount; i++) {
|
||||
values.push(Math.round(this.props.container.Progress.progress[i].value));
|
||||
sum += this.props.container.Progress.progress[i].value;
|
||||
}
|
||||
|
||||
sum = sum / this.props.container.Progress.amount;
|
||||
|
||||
fields.push(<h2>{Math.round(sum*100)/100}%</h2>);
|
||||
fields.push(<ContainerProgress pBar1={values[0]} pBar2={values[1]} pBar3={values[2]} pBar4={values[3]} />);
|
||||
|
||||
body = (
|
||||
<div className="details-progress">
|
||||
<h2>Downloading Image</h2>
|
||||
{fields}
|
||||
</div>
|
||||
);
|
||||
|
||||
} else if (this.props.container.State.Waiting) {
|
||||
body = (
|
||||
<div className="details-progress">
|
||||
|
|
|
@ -64,7 +64,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
render: function () {
|
||||
var logs = this.state.logs.map(function (l, i) {
|
||||
return <span key={i} dangerouslySetInnerHTML={{__html: l}}></span>;
|
||||
return <span key={i} dangerouslySetInnerHTML={{__html: l}}></span>;
|
||||
});
|
||||
if (logs.length === 0) {
|
||||
logs = "No logs for this container.";
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
var React = require('react/addons');
|
||||
var ContainerListItem = require('./ContainerListItem.react');
|
||||
var ContainerListNewItem = require('./ContainerListNewItem.react');
|
||||
|
||||
var ContainerList = React.createClass({
|
||||
componentWillMount: function () {
|
||||
|
@ -14,7 +13,6 @@ var ContainerList = React.createClass({
|
|||
});
|
||||
return (
|
||||
<ul>
|
||||
<ContainerListNewItem key={'newcontainer'} containers={this.props.containers}/>
|
||||
{containers}
|
||||
</ul>
|
||||
);
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
var $ = require('jquery');
|
||||
var React = require('react');
|
||||
var Router = require('react-router');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
|
||||
var ContainerListNewItem = React.createClass({
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
handleItemMouseEnter: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action');
|
||||
$action.show();
|
||||
},
|
||||
handleItemMouseLeave: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action');
|
||||
$action.hide();
|
||||
},
|
||||
handleDelete: function (event) {
|
||||
metrics.track('Deleted Container', {
|
||||
from: 'list',
|
||||
type: 'new'
|
||||
});
|
||||
|
||||
if (this.props.containers.length > 0 && this.getRoutes()[this.getRoutes().length - 2].name === 'new') {
|
||||
var name = this.props.containers[0].Name;
|
||||
this.transitionTo('containerHome', {name});
|
||||
}
|
||||
$(this.getDOMNode()).fadeOut(300);
|
||||
event.preventDefault();
|
||||
},
|
||||
render: function () {
|
||||
var action;
|
||||
if (this.props.containers.length > 0) {
|
||||
action = (
|
||||
<div className="action">
|
||||
<span className="icon icon-delete-3 btn-delete" onClick={this.handleDelete}></span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Router.Link to="search">
|
||||
<li className="new-container-item" onMouseEnter={this.handleItemMouseEnter} onMouseLeave={this.handleItemMouseLeave}>
|
||||
<div className="state state-new"></div>
|
||||
<div className="info">
|
||||
<div className="name">
|
||||
New Container
|
||||
</div>
|
||||
</div>
|
||||
{action}
|
||||
</li>
|
||||
</Router.Link>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ContainerListNewItem;
|
|
@ -0,0 +1,41 @@
|
|||
var React = require('react');
|
||||
|
||||
/*
|
||||
|
||||
Usage: <ContainerProgress pBar1={20} pBar2={70} pBar3={100} pBar4={20} />
|
||||
|
||||
*/
|
||||
var ContainerProgress = React.createClass({
|
||||
render: function () {
|
||||
var pBar1Style = {
|
||||
height: this.props.pBar1 + '%'
|
||||
};
|
||||
var pBar2Style = {
|
||||
height: this.props.pBar2 + '%'
|
||||
};
|
||||
var pBar3Style = {
|
||||
height: this.props.pBar3 + '%'
|
||||
};
|
||||
var pBar4Style = {
|
||||
height: this.props.pBar4 + '%'
|
||||
};
|
||||
return (
|
||||
<div className="container-progress">
|
||||
<div className="bar-1 bar-bg">
|
||||
<div className="bar-1 bar-fg" style={pBar4Style}></div>
|
||||
</div>
|
||||
<div className="bar-2 bar-bg">
|
||||
<div className="bar-2 bar-fg" style={pBar3Style}></div>
|
||||
</div>
|
||||
<div className="bar-3 bar-bg">
|
||||
<div className="bar-3 bar-fg" style={pBar2Style}></div>
|
||||
</div>
|
||||
<div className="bar-4 bar-bg">
|
||||
<div className="bar-4 bar-fg" style={pBar1Style}></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ContainerProgress;
|
|
@ -161,7 +161,9 @@ var Containers = React.createClass({
|
|||
<section className={sidebarHeaderClass}>
|
||||
<h4>Containers</h4>
|
||||
<div className="create">
|
||||
<a className="btn-new icon icon-add-3" onClick={this.handleNewContainer}></a>
|
||||
<Router.Link to="new">
|
||||
<span className="btn-new icon icon-add-3"></span>
|
||||
</Router.Link>
|
||||
</div>
|
||||
</section>
|
||||
<section className="sidebar-containers" onScroll={this.handleScroll}>
|
||||
|
|
|
@ -44,12 +44,13 @@ var ImageCard = React.createClass({
|
|||
$tagOverlay.fadeOut(300);
|
||||
metrics.track('Selected Image Tag');
|
||||
},
|
||||
handleClick: function (repository) {
|
||||
handleClick: function () {
|
||||
metrics.track('Created Container', {
|
||||
from: 'search'
|
||||
});
|
||||
let name = containerStore.generateName(repository);
|
||||
containerActions.run(name, repository, this.state.chosenTag);
|
||||
let name = containerStore.generateName(this.props.image.name);
|
||||
let repo = this.props.image.namespace === 'library' ? this.props.image.name : this.props.image.namespace + '/' + this.props.image.name;
|
||||
containerActions.run(name, repo, this.state.chosenTag);
|
||||
this.transitionTo('containerHome', {name});
|
||||
},
|
||||
handleTagOverlayClick: function () {
|
||||
|
@ -164,7 +165,7 @@ var ImageCard = React.createClass({
|
|||
<span className="text" onClick={self.handleTagOverlayClick.bind(self, this.props.image.name)} data-name={this.props.image.name}>{this.state.chosenTag}</span>
|
||||
</div>
|
||||
<div className="action">
|
||||
<a className="btn btn-action" onClick={self.handleClick.bind(self, this.props.image.name)}>Create</a>
|
||||
<a className="btn btn-action" onClick={self.handleClick}>Create</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -45,7 +45,7 @@ var routes = (
|
|||
</Route>
|
||||
</Route>
|
||||
<Route name="new" path="containers/new">
|
||||
<Route name="search" path="containers/new/search" handler={NewContainerSearch}></Route>
|
||||
<DefaultRoute name="search" handler={NewContainerSearch}/>
|
||||
<Route name="pull" path="containers/new/pull" handler={NewContainerPull}></Route>
|
||||
</Route>
|
||||
<Route name="preferences" path="/preferences" handler={Preferences}/>
|
||||
|
|
|
@ -97,11 +97,15 @@ class ContainerStore {
|
|||
this.setState({containers});
|
||||
}
|
||||
|
||||
// Receives the name of the container and columns of progression
|
||||
// A column represents progression for one or more layers
|
||||
progress ({name, progress}) {
|
||||
let containers = this.containers;
|
||||
|
||||
if (containers[name]) {
|
||||
containers[name].Progress = progress;
|
||||
}
|
||||
|
||||
this.setState({containers});
|
||||
}
|
||||
|
||||
|
|
|
@ -177,9 +177,16 @@ export default {
|
|||
delete this.placeholders[name];
|
||||
localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
|
||||
this.createContainer(name, {Image: imageName});
|
||||
}, progress => {
|
||||
},
|
||||
|
||||
// progress is actually the progression PER LAYER (combined in columns)
|
||||
// not total because it's not accurate enough
|
||||
progress => {
|
||||
containerServerActions.progress({name, progress});
|
||||
}, () => {
|
||||
},
|
||||
|
||||
|
||||
() => {
|
||||
containerServerActions.waiting({name, waiting: true});
|
||||
});
|
||||
},
|
||||
|
@ -309,7 +316,7 @@ export default {
|
|||
stream.setEncoding('utf8');
|
||||
stream.on('data', json => {
|
||||
let data = JSON.parse(json);
|
||||
console.log(data);
|
||||
// console.log(data);
|
||||
|
||||
if (data.status === 'pull' || data.status === 'untag' || data.status === 'delete') {
|
||||
return;
|
||||
|
@ -327,66 +334,106 @@ export default {
|
|||
},
|
||||
|
||||
pullImage (auth, repository, tag, callback, progressCallback, blockedCallback) {
|
||||
// TODO: Support v2 registry API
|
||||
// TODO: clean this up- It's messy to work with pulls from both the v1 and v2 registry APIs
|
||||
// Use the per-layer pull progress % to update the total progress.
|
||||
this.client.listImages({all: 1}, (err, images) => {
|
||||
images = images || [];
|
||||
this.client.pull(repository + ':' + tag, (err, stream) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
this.client.pull(repository + ':' + tag, {authconfig: {key: auth}}, (err, stream) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
stream.setEncoding('utf8');
|
||||
|
||||
// scheduled to inform about progression at given interval
|
||||
let tick = null;
|
||||
let layerProgress = {};
|
||||
|
||||
// Split the loading in a few columns for more feedback
|
||||
let columns = {};
|
||||
columns.amount = 4; // arbitrary
|
||||
columns.toFill = 0; // the current column index, waiting for layer IDs to be displayed
|
||||
|
||||
// data is associated with one layer only (can be identified with id)
|
||||
stream.on('data', str => {
|
||||
var data = JSON.parse(str);
|
||||
|
||||
if (data.error) {
|
||||
return;
|
||||
}
|
||||
stream.setEncoding('utf8');
|
||||
|
||||
let timeout = null;
|
||||
let layerProgress = {};
|
||||
stream.on('data', str => {
|
||||
var data = JSON.parse(str);
|
||||
if (data.status && (data.status === 'Pulling dependent layers' || data.status.indexOf('already being pulled by another client') !== -1)) {
|
||||
blockedCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
return;
|
||||
}
|
||||
if (data.status === 'Pulling fs layer') {
|
||||
layerProgress[data.id] = {
|
||||
current: 0,
|
||||
total: 1
|
||||
};
|
||||
} else if (data.status === 'Downloading') {
|
||||
if (!columns.progress) {
|
||||
columns.progress = []; // layerIDs, nbLayers, maxLayers, progress value
|
||||
let layersToLoad = _.keys(layerProgress).length;
|
||||
|
||||
if (data.status && (data.status === 'Pulling dependent layers' || data.status.indexOf('already being pulled by another client') !== -1)) {
|
||||
blockedCallback();
|
||||
return;
|
||||
}
|
||||
console.log(_.values(layerProgress));
|
||||
|
||||
if (!layerProgress[data.id]) {
|
||||
layerProgress[data.id] = 0;
|
||||
}
|
||||
console.log('layersToLoad: ', layersToLoad);
|
||||
|
||||
if (data.status === 'Already exists') {
|
||||
layerProgress[data.id] = 1;
|
||||
} else if (data.status === 'Downloading') {
|
||||
let current = data.progressDetail.current;
|
||||
let total = data.progressDetail.total;
|
||||
|
||||
if (total <= 0) {
|
||||
progressCallback(0);
|
||||
return;
|
||||
} else {
|
||||
layerProgress[data.id] = current / total;
|
||||
}
|
||||
|
||||
let sum = _.values(layerProgress).reduce((pv, sv) => pv + sv, 0);
|
||||
let numlayers = _.keys(layerProgress).length;
|
||||
|
||||
var totalProgress = sum / numlayers * 100;
|
||||
|
||||
if (!timeout) {
|
||||
progressCallback(totalProgress);
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null;
|
||||
}, 100);
|
||||
for (let i = 0; i < columns.amount; i++) {
|
||||
let layerAmount = Math.ceil(layersToLoad / (columns.amount - i));
|
||||
console.log(i, layerAmount);
|
||||
layersToLoad -= layerAmount;
|
||||
columns.progress[i] = {layerIDs:[], nbLayers:0, maxLayers:layerAmount, value:0.0};
|
||||
}
|
||||
}
|
||||
});
|
||||
stream.on('end', function () {
|
||||
callback();
|
||||
});
|
||||
|
||||
layerProgress[data.id].current = data.progressDetail.current;
|
||||
layerProgress[data.id].total = data.progressDetail.total;
|
||||
|
||||
// Assign to a column if not done yet
|
||||
if (!layerProgress[data.id].column) {
|
||||
// test if we can still add layers to that column
|
||||
if (columns.progress[columns.toFill].nbLayers === columns.progress[columns.toFill].maxLayers) {
|
||||
columns.toFill++;
|
||||
}
|
||||
|
||||
layerProgress[data.id].column = columns.toFill;
|
||||
columns.progress[columns.toFill].layerIDs.push(data.id);
|
||||
columns.progress[columns.toFill].nbLayers++;
|
||||
}
|
||||
|
||||
if (!tick) {
|
||||
tick = setTimeout(() => {
|
||||
clearInterval(tick);
|
||||
tick = null;
|
||||
for (let i = 0; i < columns.amount; i++) {
|
||||
columns.progress[i].value = 0.0;
|
||||
|
||||
// Start only if the column has accurate values for all layers
|
||||
if (columns.progress[i].nbLayers === columns.progress[i].maxLayers) {
|
||||
let layer;
|
||||
let totalSum = 0;
|
||||
let currentSum = 0;
|
||||
|
||||
for (let j = 0; j < columns.progress[i].nbLayers; j++) {
|
||||
layer = layerProgress[columns.progress[i].layerIDs[j]];
|
||||
totalSum += layer.total;
|
||||
currentSum += layer.current;
|
||||
}
|
||||
|
||||
if (totalSum > 0) {
|
||||
columns.progress[i].value = 100.0 * currentSum / totalSum;
|
||||
} else {
|
||||
columns.progress[i].value = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
progressCallback(columns);
|
||||
}, 16);
|
||||
}
|
||||
}
|
||||
});
|
||||
stream.on('end', function () {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
.container-progress {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 4px solid @brand-primary;
|
||||
border-radius: 10px;
|
||||
transform: rotate(180deg);
|
||||
.bar-bg {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 22px;
|
||||
background-color: @gray-lightest;
|
||||
width: 4px;
|
||||
height: 50px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.bar-fg {
|
||||
background-color: @brand-primary;
|
||||
width: 4px;
|
||||
height: 0px;
|
||||
border-radius: 10px;
|
||||
transition: 0.3 all;
|
||||
}
|
||||
.bar-1 {
|
||||
left: 21px;
|
||||
}
|
||||
.bar-2 {
|
||||
left: 32px;
|
||||
}
|
||||
.bar-3 {
|
||||
left: 43px;
|
||||
}
|
||||
.bar-4 {
|
||||
left: 54px;
|
||||
}
|
||||
}
|
|
@ -29,16 +29,31 @@
|
|||
position: relative;
|
||||
}
|
||||
.create {
|
||||
display: flex;
|
||||
flex: 1 auto;
|
||||
text-align: right;
|
||||
justify-content: flex-end;
|
||||
margin-right: 20px;
|
||||
margin-top: 3px;
|
||||
.btn-new {
|
||||
font-size: 24px;
|
||||
color: @brand-action;
|
||||
transition: all 0.25s;
|
||||
&:hover {
|
||||
color: darken(@brand-action, 15%);
|
||||
a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
&.active {
|
||||
.btn-new {
|
||||
opacity: 0.3;
|
||||
&:hover {
|
||||
color: @brand-action;
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-new {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
color: @brand-action;
|
||||
transition: all 0.25s;
|
||||
&:hover {
|
||||
color: darken(@brand-action, 15%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
@import "container-settings.less";
|
||||
@import "spinner.less";
|
||||
@import "animation.less";
|
||||
@import "container-progress.less";
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
|
|
|
@ -122,9 +122,12 @@
|
|||
}
|
||||
|
||||
.details-progress {
|
||||
margin: 20% auto 0;
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
flex: 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
margin-top: -70px;
|
||||
h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue