From 7366ecde20695ce8b54783c54f2d8abcc67953c3 Mon Sep 17 00:00:00 2001 From: Michael Chiang Date: Wed, 20 May 2015 18:51:04 -0700 Subject: [PATCH 1/6] Updating to be more complete - Xcode --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b49261e426..cf4dbb202e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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), From 75229d67118bbc33f86c10c067e553490f1972cc Mon Sep 17 00:00:00 2001 From: Michael Chiang Date: Thu, 21 May 2015 17:59:38 -0700 Subject: [PATCH 2/6] Security Disclosure. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 65e8ecef80..c039e69f17 100644 --- a/README.md +++ b/README.md @@ -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). From ca94af0437fec0d2e82f2b5328b5345d739b376e Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 21 May 2015 23:47:03 -0700 Subject: [PATCH 3/6] Got rid of ContainerListNewItem. --- src/components/ContainerList.react.js | 2 - src/components/ContainerListNewItem.react.js | 54 -------------------- src/components/Containers.react.js | 4 +- styles/left-panel.less | 29 ++++++++--- 4 files changed, 25 insertions(+), 64 deletions(-) delete mode 100644 src/components/ContainerListNewItem.react.js diff --git a/src/components/ContainerList.react.js b/src/components/ContainerList.react.js index 7ff8504b3f..4a042b4c81 100644 --- a/src/components/ContainerList.react.js +++ b/src/components/ContainerList.react.js @@ -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 (
    - {containers}
); diff --git a/src/components/ContainerListNewItem.react.js b/src/components/ContainerListNewItem.react.js deleted file mode 100644 index 237e620c4a..0000000000 --- a/src/components/ContainerListNewItem.react.js +++ /dev/null @@ -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 = ( -
- -
- ); - } - return ( - -
  • -
    -
    -
    - New Container -
    -
    - {action} -
  • -
    - ); - } -}); - -module.exports = ContainerListNewItem; diff --git a/src/components/Containers.react.js b/src/components/Containers.react.js index 03319d7d20..a588db73c9 100644 --- a/src/components/Containers.react.js +++ b/src/components/Containers.react.js @@ -180,7 +180,9 @@ var Containers = React.createClass({

    Containers

    - + + +
    diff --git a/styles/left-panel.less b/styles/left-panel.less index 769fe64b4b..3ceb44068a 100644 --- a/styles/left-panel.less +++ b/styles/left-panel.less @@ -28,16 +28,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%); + } } } } From 55bdaa5c50e3e92369a3eab4ba5cc937bc47d0fe Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Fri, 22 May 2015 17:00:23 -0700 Subject: [PATCH 4/6] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c039e69f17..ff1fcca754 100644 --- a/README.md +++ b/README.md @@ -51,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. From 2ccee4a5055f99029ca40bb2dde23ed5882bb344 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Fri, 22 May 2015 17:40:09 -0700 Subject: [PATCH 5/6] Added Container Progress component. --- src/components/ContainerProgress.react.js | 41 +++++++++++++++++++++++ styles/container-progress.less | 35 +++++++++++++++++++ styles/main.less | 1 + 3 files changed, 77 insertions(+) create mode 100644 src/components/ContainerProgress.react.js create mode 100644 styles/container-progress.less diff --git a/src/components/ContainerProgress.react.js b/src/components/ContainerProgress.react.js new file mode 100644 index 0000000000..722f98b9d7 --- /dev/null +++ b/src/components/ContainerProgress.react.js @@ -0,0 +1,41 @@ +var React = require('react'); + +/* + + Usage: + +*/ +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 ( +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + ); + } +}); + +module.exports = ContainerProgress; diff --git a/styles/container-progress.less b/styles/container-progress.less new file mode 100644 index 0000000000..a8bf96613a --- /dev/null +++ b/styles/container-progress.less @@ -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; + } +} diff --git a/styles/main.less b/styles/main.less index 87f3db3143..a1cee9e15d 100644 --- a/styles/main.less +++ b/styles/main.less @@ -16,6 +16,7 @@ @import "container-logs.less"; @import "container-settings.less"; @import "animation.less"; +@import "container-progress.less"; html, body { height: 100%; From 4f80c5d0db144cce9cd3842e2d87b76bdf393a6c Mon Sep 17 00:00:00 2001 From: Adrien Duermael Date: Fri, 22 May 2015 18:00:57 -0700 Subject: [PATCH 6/6] Improved pull image loading --- src/components/ContainerHome.react.js | 36 +++++--- src/stores/ContainerStore.js | 4 + src/utils/DockerUtil.js | 128 +++++++++++++++++++++----- 3 files changed, 132 insertions(+), 36 deletions(-) diff --git a/src/components/ContainerHome.react.js b/src/components/ContainerHome.react.js index 08016322d1..bcc2f07f25 100644 --- a/src/components/ContainerHome.react.js +++ b/src/components/ContainerHome.react.js @@ -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'); @@ -51,22 +52,29 @@ var ContainerHome = React.createClass({ ); } else if (this.props.container && this.props.container.State.Downloading) { if (this.props.container.Progress !== undefined) { - if (this.props.container.Progress > 0) { - body = ( -
    -

    Downloading Image

    - -
    - ); - } else { - body = ( -
    -

    Downloading Image

    - -
    - ); + + 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(

    {Math.round(sum*100)/100}%

    ) + fields.push(); + + body = ( +
    +

    Downloading Image

    + {fields} +
    + ); + } else if (this.props.container.State.Waiting) { body = (
    diff --git a/src/stores/ContainerStore.js b/src/stores/ContainerStore.js index d3b63099a0..304fec7131 100644 --- a/src/stores/ContainerStore.js +++ b/src/stores/ContainerStore.js @@ -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}); } diff --git a/src/utils/DockerUtil.js b/src/utils/DockerUtil.js index ccc6cb8644..f5315de6b9 100644 --- a/src/utils/DockerUtil.js +++ b/src/utils/DockerUtil.js @@ -178,9 +178,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}); }); }, @@ -310,7 +317,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; @@ -344,6 +351,9 @@ export default { return !existingIds.has(layerSize.Id); }); + console.log("existingIds:" + existingIds.size) + console.log("layersToDownload:" + layersToDownload.length) + this.client.pull(repository + ':' + tag, (err, stream) => { if (err) { callback(err); @@ -351,16 +361,45 @@ export default { } stream.setEncoding('utf8'); + // layerProgress contains progression infos for all layers let layerProgress = layersToDownload.reduce(function (r, layer) { if (_.findWhere(images, {Id: layer.Id})) { - r[layer.Id] = 1; + // If the layer is already here, we set current and total to 1 + r[layer.Id] = {current:1, total:1, column:-1}; } else { - r[layer.Id] = 0; + // At this point, the total layer size is unknown + // so we set total to -1 to avoid displaying it + r[layer.Id] = {current:0, total:-1, column:-1}; } return r; }, {}); - let timeout = null; + + + let layersToLoad = _.keys(layerProgress).length; + + console.log("nbLayers:" + layersToLoad) + + // Split the loading in a few columns for more feedback + let columns = {}; + columns.amount = 4; // arbitrary + columns.progress = []; // layerIDs, nbLayers, maxLayers, progress value + columns.toFill = 0; // the current column index, waiting for layer IDs to be displayed + + for (let i = 0; i < columns.amount; i++) + { + let layerAmount = Math.ceil(layersToLoad / (columns.amount - i)) + layersToLoad -= layerAmount; + columns.progress[i] = { layerIDs:[], nbLayers:0 , maxLayers:layerAmount , value:0.0 }; + } + + + + // scheduled to inform about progression at given interval + let tick = null; + + + // data is associated with one layer only (can be identified with id) stream.on('data', str => { var data = JSON.parse(str); @@ -374,34 +413,79 @@ export default { } if (data.status === 'Already exists') { - layerProgress[data.id] = 1; + + //layerProgress[data.id].current = 1; + //layerProgress[data.id].total = 1; + } else if (data.status === 'Downloading') { - let current = data.progressDetail.current; - let total = data.progressDetail.total; + + // aduermael: How total could be <= 0 ? + + // if (data.progressDetail.total <= 0) { + // progressCallback(0); + // return; + // } else { + + 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 == -1) + { + // 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 (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; + if (!tick) { + tick = setInterval( function(){ + // console.log(JSON.stringify(layerProgress)) - var totalProgress = sum / numlayers * 100; + // update values + 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; - if (!timeout) { - progressCallback(totalProgress); - timeout = setTimeout(() => { - timeout = null; - }, 100); + 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); + + },33); } } + }); + stream.on('end', function () { + + clearInterval(tick); callback(); + }); + }); }); });