From 1233eec3dd47307acfa74a5cc85e290234b4839b Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sun, 18 Jan 2015 21:58:46 -0500 Subject: [PATCH] Pull & run a container from DockerHub --- app/ContainerDetails.react.js | 47 ++++---- app/ContainerStore.js | 159 +++++++++++++++++++++++----- app/Containers.react.js | 59 ++++++++--- app/Setup.react.js | 1 + app/boot2docker.js | 5 + app/images/downloading-arrow.png | Bin 0 -> 276 bytes app/images/downloading-arrow@2x.png | Bin 0 -> 555 bytes app/images/downloading.png | Bin 0 -> 619 bytes app/images/downloading@2x.png | Bin 0 -> 1360 bytes app/images/error.png | Bin 0 -> 584 bytes app/images/error@2x.png | Bin 0 -> 1136 bytes app/images/paused.png | Bin 0 -> 726 bytes app/images/paused@2x.png | Bin 0 -> 1594 bytes app/images/stopped.png | Bin 0 -> 571 bytes app/images/stopped@2x.png | Bin 0 -> 1177 bytes app/index.html | 1 + app/main.js | 15 +-- app/styles/main.less | 75 ++++++++++--- browser/main.js | 1 - 19 files changed, 279 insertions(+), 84 deletions(-) create mode 100644 app/images/downloading-arrow.png create mode 100644 app/images/downloading-arrow@2x.png create mode 100644 app/images/downloading.png create mode 100644 app/images/downloading@2x.png create mode 100644 app/images/error.png create mode 100644 app/images/error@2x.png create mode 100644 app/images/paused.png create mode 100644 app/images/paused@2x.png create mode 100644 app/images/stopped.png create mode 100644 app/images/stopped@2x.png 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 =
; + + // Synchronize all animations + var style = { + WebkitAnimationDelay: (self._start - Date.now()) + 'ms' + }; + + if (downloading) { + state =
; + } else if (container.State.Running && !container.State.Paused) { + state =
; + } else if (container.State.Restarting) { + state =
; + } else if (container.State.Paused) { + state =
; + } else if (container.State.ExitCode) { + // state =
; + state =
; } else { - state =
; + state =
; } return ( - +
  • {state}
    @@ -61,7 +94,7 @@ var ContainerList = React.createClass({ {container.Name.replace('/', '')}
    - {container.Config.Image} + {imageName}
  • @@ -94,7 +127,7 @@ var Containers = React.createClass({ } }, handleClick: function () { - // ContainerStore.create('jbfink/wordpress', 'latest'); + ContainerStore.create('dockerfile/ghost', 'latest', 'testghost'); }, render: function () { var sidebarHeaderClass = 'sidebar-header'; @@ -102,12 +135,12 @@ var Containers = React.createClass({ sidebarHeaderClass += ' sep'; } return ( -
    +
    -

    containers

    +

    containers

    }>
    diff --git a/app/Setup.react.js b/app/Setup.react.js index 934b86e948..f04f58a969 100644 --- a/app/Setup.react.js +++ b/app/Setup.react.js @@ -9,6 +9,7 @@ var boot2docker = require('./boot2docker.js'); var virtualbox = require('./virtualbox.js'); var util = require('./util.js'); var docker = require('./docker.js'); +var ContainerStore = require('./ContainerStore.js'); var setupSteps = [ { diff --git a/app/boot2docker.js b/app/boot2docker.js index b788db41a2..f4ef4858a9 100644 --- a/app/boot2docker.js +++ b/app/boot2docker.js @@ -156,6 +156,11 @@ var Boot2Docker = { } }); }, + createScratchImage: function (callback) { + cmdExec([Boot2Docker.command(), 'ssh', 'tar cv --files-from /dev/null | docker import - scratch'], function (err, out) { + callback(err); + }); + }, stats: function (callback) { var self = this; self.state(function (err, state) { diff --git a/app/images/downloading-arrow.png b/app/images/downloading-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..dfca72b6ae952c1b0579c128d60ae5dabac5ae73 GIT binary patch literal 276 zcmV+v0qg#WP)Px#&PhZ;R5%f1U>FGCe&$m#2oqyB6H$s7|3@+bF;QxXQACOwqFgv?Al?w5dKj|c zvd-h|M|TEBhPr>h|Hr`8I30T<&CbrY>_5Zbi93u1*TKY*X`C+n%lQ8-!+%C2CRWCM z{}~urftZDjoo&y51_mp}zYK4Y4MAozB6HB$?q@!FGcq!+XJq{U8)(o!#{Z0*{}}#7 z?KBoxg{~IH$7>)+k>}|TAq-4REB-SwFfsgNNZet$!9NqNp|PegQieI0wx>RuJgm2}zc9KF(|+q-#r z_0}t5t_#In7k&S7=V9WFm)h?+b=Mc{d2egK@BH=ig&*V^e(h8~aA#}z?&*5(k1Ia7 zzfH+szy0TxH|6Hq`=ux6zkRvo%*R@7sSmSHJNma?(VhOPD8uQAqxGNa{NpLtCrVB+ zRd^N5=Gx*?=^EhTF(sS@8*{o<-cjwX8tmJSGU$Iwj4!(|VM*L+hU9F9W08tBm3Awx zX{vwiI2kj|a2rpLy!z^1TlGZaX)CUnOFp+;<;(mb_;tYT^*j7yx)hQN+^dY2smY!! z7pnTZIlO-5tqAvq#d&jVPtDo1l*4KB*2V_@Zw2RW>RHa+{+{uINxjpvwfgTe~DWM4fXsY+8 literal 0 HcmV?d00001 diff --git a/app/images/downloading.png b/app/images/downloading.png new file mode 100644 index 0000000000000000000000000000000000000000..2dd4acfa27f7d3cce38bb402b0824740cb280b5f GIT binary patch literal 619 zcmV-x0+juUP)Px%B}qg8HlNv=78ih7`sq~OP3WA`;cGE)-g2Z#7q7;-` z@S-&It+UQy5;$*UELk%eX-CghKv)ltF`2q zq(Vy1HUsiCu;)sU0_WJ^zJP>+gZL3e`l({7<+U%`$n4pJEZ?KxPIxG3d>=Lot6f|u z7%*=yT54J0MXM~di|{BL$!e|EY&W}^cpEq~iiIcP-}X%3rQBwqJ+{-Om>!|ZZ<Px)3Q0skR9Fe^SZ`=sRTRJHB~9BjZ5q}TTPvtr-B7jIY#$VbieD7= zhryJoA4E_XQB;J1gNYyfgM*G+pV+T<2_)x-oPJPKN88U0m0+c1=j$yZ-L$ z?rT!pq)l!6C6DmVpL@?azjw~L_r4d#3fIaqKr1t6%oYCAT1~b#&iE!Ge!s@a#2Ak< zPL~o>+Tei4F|m>uc>`<@k8d?A=0n8!PDW&12n6RW39#REfts0=SOCzo#707b@} zWIIB{_QJW6D}Wg1;>X!%G&XiHxU)X76dO5bSpdD^XpKUa6UdlLI=Dwg&@kuxI%8A} zr&|CDC1*t$^Em6DnT$U*;Hmy6L!U)hGQgJaCmc2_4HMBu=?@@AnW_b*63Ont7Uz`I z3)&g_t4yh~9$>^?M-ZnRLn?=OocTk|_Hn7pMwbS#yW8a7s)(h&k2MSxeNoRiKz;1_tn7vx+` zRi6&I9p6fMF`5(L_H)tla%=HbStfw%>-jC@V_jY9Von#)lP?k*M2kEj#<9Y0!skh} z$$6TdJ{8KvmE`lKT1xwXOf;$}tO#K1;l~efcHvL#5Jg4oT<6ejS8vk>Y+2BW+x$!P zU0JcE1x`A|lZkPx+R2_1M;t5=&`UJmCSMsRlHE+E_Yg;vU%$jwgg$Q;t&{;!Yzb6V z`Y!rnFEO%7gIOyc3A*f!8M^;b@P}iEm_lz$mzv6747u&cjUM!0xLs#*mX2UMdG$Zw zbSt_hjaP%tOpUyc6@5wiCWXBzcpad~9JdsOP;sL{(I0WtHH=Z#5VerSGR21H*~>H5{+iPJ?a-3N8woaz)Om3q;0 zWkP{crquY%9({0~Zk%{}E7=uB%42C*e(BwQ->o!xJy_$-2XE+O3J#v?ITZZz9KSvs zakfQ9=1jSvU~&8{6`W5N4zL3pY@5avEbCcGH3j`E@Bz6=hm`6s`H2$&Rfr&BSNbD3=@=n3y zN?`Df#MJmF1D=QCv#Qm*`-kp4$xNnqfMkb^f_=CbTb&Ow*8z51h}D}dYzTOb_c3gk z(>O(tUAk!)GJaQO$xr)SRac~LIn);!-^R$a2VqMP7(d>Cw#xhfoQ5(5;`thSlmbhfg5e{mmbIVpzZt-Iua-nfW+4W4L z?0SiXVO|$c?ZQ7JdWnTS{6!U2JKfXdIID|!IV~6!GEm4sAp?aB6f%%41OEbiDaL-C SDg?&>0000Px%0!c(cR5%f>Rk2DVQ53!RO`?s=!1|q=iYNN_q}^=m}1t^w|u&{ zi$+;!lvoBx_>)azxzPQQFzx=nq}_qzLam<8^}yBe1j@C^|#hUSUnUPQkC z+$(Nqq8}tizZExg+1wN}uMlyI@6&R7sjz??D~K}jYY_8&8EG#S52FH&_Sc!*pfUUl zqMj$?D4%fwpS$Q3C1^P_ANPvujRfNGn`~~H*{tDA&o1eUvA2l}&~rhVainILpZE$AI1i=9pInXk;&-CHp!7BkwvO=!*EdR^qt18l(L@0)H7xqs~FcFjz zmI@R1p=mo0woa0${2iM3nog(7dX^OfZ$c!MaxzK8UT{^*z~}(=Epu+fLcm_LT;+g^ zTxc0g4KyV&_rp%F5sBtlj&h*^t#`Mcy=8b128So%P1wn8Qa)nH349E0Fvh}ZgQVF^ ze(7oAXi*qn>HsvY=iQzwLXOkcjmxuVrey+y*lMY;=svqF6hsi~tpoNyL5RNPFW?bA W%Lja$?OIX*0000JX~{ literal 0 HcmV?d00001 diff --git a/app/images/error@2x.png b/app/images/error@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e075bb329550c8e0513093d1368b942e7ee45c8f GIT binary patch literal 1136 zcmV-$1dscPP)Px(DoI2^R9FeknNLVmK^({DH|t*%R}oB>Iu#uvPZ8BkBSbn>M$$o|i+PB;R5!tY z(5*T}2k{c&A&jIBArj@SLOK;eQKw>C1<@5ncWvMF`_8^MJMZmc>z{q?yXt5S*6Y^MaM{}m?9AqiekDsQgKout^~!2)JG>xFB;#B{n&{VZKqPA z^GZuC)!yJr-Gao??bH=Yd{#nCt*iBitus#+imh7|;6i&(eRWmzwjgkxeScspedTOK5rpH{oC%=kFIF1<2B-bXIKTNC?c%PTGc%ru<60Pf%QVjCT z*1Q5jOeT{{R}Z|KTo1l01V}4g{ZF}I5H7_%c9#eF10LjS!G?-S^CYX|Np_2Sbb!lO z2kJ~VOpjgWJYP@d0rF=ZA}NOhl@scZ(;a<1{yvbuqh#_--$|;FQ(W2Co62#9`E!rh zjrsuH3Gpm2=00t1IA@z@$A8%scN;IZ_wBE&k*~O&Hq#alNuD^D?1^Ilv6R$QH+~Py z)&#>H-)lGz$c3Eabgn0Pc5kGX4|z+F^a^L!Ip0!}!e=cN9CXn(G&oFo9Oxj;;$~<- z>=0OlO(702A@*v_+3f~nXh6%C&n%jWGc{nXsJR)~ArhFazDgxbxLOHM0xZ~4k=B5` z4qxzI;uq`OPL~q9onm8XLDMeJlLMb#NSsRi8^20p5w;755oa}3+Gz1%M4y;&WHOl? zVEZa|hp_AIHijm&?RLN>ah|g?vA6otVI~6FV3Rm(+6>tE?R1!NP#bKN?7H@q6deu| zq*yIQ>YFsj^kQp;S&l7)6vRWbTpwn`I{3R>`M_2I_r>d=8G{+&bnt0n_J2V0X{tRU zq*-dfCSeV27=28)GeV4m+F+CLtv&|!5WaMniGViPBuur<0D}V$VdsSrLlfGz7TBbA z{A{XoFQ^IzCzlQ<%4%B9kD0)xPcyvEz#o_g?h6LTWRy67mh1S8lE{q3!Zp z;l51i5(lo9Irf7EOpw)p`mThpBWw;H4+C&tc3vrB?eMZTG}k<{XeQ3lfOvzu5z=o0oTt{SYA=v1b(BV9F4e=L#wI*RxN|(q00000Px%kV!;AR5%f>RZmEiQ4pVbyDM($t_d3S%5foVW4neoNR0wtR z6p~bztVnDJG^paB6HTtHL|7N{Q6o8et35}wE+VxeBkTx)K(aB znUv&~lAGD3)RiqkRWxkC01F@ZdPFW|q>0@$)ndiUnbyP8?>xYE>+kRV0|CPmntFo} zT|oMb12}r1aULmHp{vn&dTcuqzYUQZrU$6$m*ql_!D)ImyZL_4RS*X!}VJlESCj_l7VW&`~bsses!B)2P; z62(F(I`JVLEN*VbMeLvmO={#ZW6ZN$Ciyn|t?xwD$1M#_O)*bKgi9)PluD&zZAU(S z@(lhpyV!q`jkybAXA}CRrJ2_}i^SBYx~N9DYP9#>0}u!V2ORJpe@N9@>K2j_a%6On zh_&8yT3w=WV{0dskjEitFJ0blI)G{IOs_>crf#*w)9q5O^oXsTvlV+*d*n0h%JO2* z8{2ivUXGr-B`||xWfU`s+5F$jp*F|r>s@0mLS&EnUB8qZVZ%d^^|0nJ0chI%#s)fp z!p+TbOTkm9w{G6+U`lrH>C{bc%4&o#;@Ry5`xhZdck>DO1D}X$on^8$fB*mh07*qo IM6N<$g51tk{r~^~ literal 0 HcmV?d00001 diff --git a/app/images/paused@2x.png b/app/images/paused@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..dea41b188163933d2df021fa901fe3dddda7dc2c GIT binary patch literal 1594 zcmV-A2F3Y_P)Px)^hrcPR9FekSzm0ERTMw>Yu9aV?FvR1bDJU~Ar$0+nEmM<gb|2|KA8A6 zln0eJ6t*QJCOV$Th%r29LZV_^z-WSrA&_oLmOUuU7#R^-vuq$b)-Bt*_FjMI`}+NB z%WS(2cayH?o_o(Zzn*)~x%b=&DzZ&Q+cs%Ldj*M|B&bf2sDmp0a2pq-#!1n*A~hn3 z&IcQZZW=>{{S}2wpBQUfSLqXP0aynKv8@nUQtCQ@4$LLgsoJKYJNe4y1@O*jsJb#x z^R6Q82n4G0Ri0~}BAO8-Kbo7q-?uI_I+JUTllPwhh+y zhQY6w(zO&o5s$SUBNE5)$)2ZF0{lxv@bNrqtj}KxvS}E>9x5=i*aMW(q>iH5%u+3~&p)tSbeMt+H;m zfnA6Foo+~HI+k=-2goM`Ct-7r@vVAGwddS=-95vG>UDA%3u9uQ4zSWkhq2c734x{M zXmtv$B(2-h)0bg|vG9?~JsqG%VxJp;>?t;txb@`MGi=Eo=WGD$;J)BniAQK-)*w!Z z#6t2h?2o*XuqC%l!bs-cyUr6QrDT%^q5w<*uq zyfA8BGIhQrQ9B)L9|Vv#jHMfi}Vn1Halovt-;LZN&X!!EI+u=~=c&@(i1T%gNb~lzIhROA|fWn07NwgvaxtIgXpd?*JG^ zXp3hprjq*tjYH?nej(poU8KGJMtdhB`-JYnoWd$fN({pIh@#ZE5T1FV7au!rUU5C%DkslGwopnQK)`*2Yt?)qni!iN zHCX0rSmte(u#$(c%%`n>iHOnBkgwifvjeu8kv=v9bP!pj8nHTwe77F^Z@*;n(*}s@3kA++rM}UGLZc{EX*m6{uo%Tu0JnLIMU07idc^Q^1lHTG@=+QZ z)t4=>l9X=CQI0Ye#$*BHw-SDiaDxy>x%zVRO5ZbVpbT|1#$vwTSUHE^N^mMnTJuo1 za%spZ!{U{gVBP8R)2@-ZXksksW&zZzD7=-RZia``4Q}Vsa3w&p&UD%7cxixbU@$8> zMqvTuj!}=QZl|&jn0g+2pt{}-ZPv;qr=yX^w s^qpt-10@^-EBT1-Fk_Jq>)#On0wr#i;iQfGWdHyG07*qoM6N<$f@0JRF8}}l literal 0 HcmV?d00001 diff --git a/app/images/stopped.png b/app/images/stopped.png new file mode 100644 index 0000000000000000000000000000000000000000..2b64d4a3cda86b9b7f3f2d5ca79e8beb9a862abb GIT binary patch literal 571 zcmV-B0>u4^P)Px$^+`lQR5%f>Rl7<9Q4pQEs}T#ax3CceU!W$1C|XzqEX2n*2qM}jD3&Qj!AcRq zZ}5SIU}2#s7KsYJDhn-a#Y&Ctj&m-#VTp0GpbMM5J9Ez2J9B3yOl7XkY___CyiqbV zD#R&;G!$UZOp>L5cCX3bolK=ZOX*x{ILTd&8Il!dZYuSK-$mphAfE3}cJ74j#=T@R z8FQAqT=tl@AtofZfynGvK4|-ReW}BZHEi$!11vb;I-TMU;UMODb>vB8?r?u^LxF(< zhMy2f506YKepT%W#jo@xyB2`4f(R2|)AziHKh&2@9>H1ZI?d$5r2(P{ZT*(BC}D8G zwHVbhg*v0)Qx2nuQ9j3o7N1^Rz3a);L=ocpgy^GKKXFNwa8(L;)Vmo)JJ8d8SBX&0 ze|&z?T9q)j&@#3+b!DnKT@D<_=D5%@XmpU6X#yP(M<9#=2V84YErY3nqGaMW+fHG0 zj5RpmLIYa=-g-7fJA$;qbwdgCCfX?~eE5KIeT?DY#+cn9A_GXbzf_nwgmyv)plJQK z*C$}S+WJNu0CI*fVS|{q-=gyEWGV_;r~dqP!2S?~@L&D{z5#7c4UvIi&_e(K002ov JPDHLkV1f~E{73)* literal 0 HcmV?d00001 diff --git a/app/images/stopped@2x.png b/app/images/stopped@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..776f0c8aeae5ab5ccf84a46e6a16d81f3e170670 GIT binary patch literal 1177 zcmV;K1ZMk*P)Px(Q%OWYR9FekSv^b?K@^@DkOM)K7=Lo1(^wcS5J)bZCSFBCp`H0c5lT4@7CL9-n^Z8-*>a~ z_q~&1jZfyqiycC#!&1n03K7>r#!?z>eLay6|pnaoz* z(0E2_(JQ6cvzoqIi`QC;M{;iVRx+9VTx#Ev2&U8Nrn=@$=Y^CPs6$h!!Nb~VA!dZu z*YnfgZ=_PGnXoq8_g8|?M@Nq;S#z5@ZQ~uwU{5-Fsc(OK`@@xDtwc~}M#nEnDKAri zuy%R+Y-t*eCyo|xgd=l{Kfht#iQ4roaiMfvNwJ$o{SZxu6uIm0F7Rl2`jhQ#SIJL< zTl|Cpi~GaF4XyPJcZk>VqApr|(%QK?wV1!#lSoV#y$^8=X0y#3YwJ!)sV>n^-Co>8 z4@}I@oj%dkH5YsrMo%A8X9WcuzAZlt<*&V zj`{oOfv$aRC&}j<$s&UESufLPz2fUkqvnXEUYajipMQ-Nn%4;>Y90kglRozq`rQ3I ztdF3(ARbTy&#%R+gknX9B>gE7{IbTsNCjqF)l+8 zCj>TO)<|GMlxxoMxIr5j!1DQti{V7h1{N@Rdz|;EE8I!~2Y()$FmD`%?PBIF7C3>M!j;K7QR4V2 z^@QO4{?!JiIXZbD;zq}%xzBlSI$OO;#8v`UMI%^E1c^&r(cQs!d#Eac#7)sT__3-;aWcfMc3FMN!W>*$(y0TTZBKc- zsO)DW;|FVMWZL3^8|I+7rdy}ErrS!yWe9do8Qsc>kcQyZNbLYNxG9KgP67f48iM)GUnOl|8O6syEGI({ z=?K~kJ(W+=5bBGyuc&FTm>dNcZ~~|yXmTZ#j^HdH4WYicuM##efaQx#Tx|Zv5X4L5 zN~oB$4e5N2K^j8XSYNqI#0CbiSnNrci*XeQLPa50LSFCaqhW_MtOQ9{(Lr|dOao+R zpH*~VSRjZB^eA#AunQ0ARw6l4&d-flh#AntbXnH@ToZ!>jc(CX-Kh`It@h-R9n$Je zzgdPTX}o?LXgl8 rXz4%pu`h78RxF8bL3kO}-w?k6XulG>LJ~K000000NkvXXu0mjfDwrxj literal 0 HcmV?d00001 diff --git a/app/index.html b/app/index.html index b33a677446..5b3d4ba12d 100644 --- a/app/index.html +++ b/app/index.html @@ -2,6 +2,7 @@ + diff --git a/app/main.js b/app/main.js index 6f4173ba60..11d5f92eaf 100644 --- a/app/main.js +++ b/app/main.js @@ -21,16 +21,13 @@ var NoContainers = React.createClass({ render: function () { return (
    - + No Containers
    ); } }); var App = React.createClass({ - componentWillMount: function () { - ContainerStore.init(); - }, render: function () { return ( @@ -53,8 +50,14 @@ var routes = ( Router.run(routes, function (Handler) { boot2docker.ip(function (err, ip) { - docker.setHost(ip); - React.render(, document.body); + if (!err) { + docker.setHost(ip); + ContainerStore.init(function () { + React.render(, document.body); + }); + } else { + React.render(, document.body); + } }); }); diff --git a/app/styles/main.less b/app/styles/main.less index f5a4146218..d2e3fe941a 100644 --- a/app/styles/main.less +++ b/app/styles/main.less @@ -188,9 +188,9 @@ li { vertical-align: middle; - margin: 11px 24px 0px; + margin: 14px 24px 0px; border-bottom: 1px solid #efefef; - padding-bottom: 11px; + padding-bottom: 14px; display: flex; flex-direction: row; @@ -209,34 +209,65 @@ } .image { color: #999; - font-size: 12px; + font-size: 10px; font-weight: 400; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } } .state { margin-top: 8px; display: inline-block; + position: relative; + min-width: 20px; + height: 20px; + } + + .state-error { + .at2x('error.png', 20px, 20px); + } + + .state-stopped { + .at2x('stopped.png', 20px, 20px); + } + + .state-paused { + .at2x('paused.png', 20px, 20px); + } + + .state-downloading { + .at2x('downloading.png', 20px, 20px); + overflow: hidden; + + .downloading-arrow { + width: 20px; + height: 20px; + .at2x('downloading-arrow.png', 20px, 20px); + position: absolute; + // background-repeat: repeat; + -webkit-animation-name: translatedownload; + -webkit-animation-duration: 1.8s; + -webkit-animation-iteration-count: infinite; + -webkit-animation-timing-function: linear; + } } .state-running { - display: inline-block; - position: relative; - width: 20px; - height: 20px; .at2x('running.png', 20px, 20px); overflow: hidden; // -webkit-mask-image: -webkit-radial-gradient(circle, white, black); .runningwave { position: absolute; - width: 44px; + width: 40px; height: 20px; left: -20px; .at2x('runningwave.png', 20px, 20px); // background-repeat: repeat; - -webkit-animation-name: translate; - -webkit-animation-duration: 6.0s; + -webkit-animation-name: translatewave; + -webkit-animation-duration: 8.0s; -webkit-animation-iteration-count: infinite; -webkit-animation-timing-function: linear; } @@ -248,6 +279,7 @@ height: 20px; .at2x('restarting.png', 20px, 20px); background-repeat: repeat-x; + -webkit-animation-delay: -1s; -webkit-animation-name: rotate; -webkit-animation-duration: 3.0s; -webkit-animation-iteration-count: infinite; @@ -478,7 +510,7 @@ html, body { height: 100%; } -@-webkit-keyframes rotate { +@-webkit-keyframes translatedownload { from { -webkit-transform: rotate(0deg); } @@ -487,7 +519,7 @@ html, body { } } -@-webkit-keyframes translate { +@-webkit-keyframes translatewave { from { -webkit-transform: translateX(0px); } @@ -495,3 +527,22 @@ html, body { -webkit-transform: translateX(20px); } } + +@-webkit-keyframes translatedownload { + 0% { + -webkit-transform: translateY(6px); + opacity: 0; + } + 25% { + opacity: 1; + -webkit-transform: translateY(6px); + } + 50% { + opacity: 1; + -webkit-transform: translateY(20px); + } + 100% { + opacity: 1; + -webkit-transform: translateY(20px); + } +} diff --git a/browser/main.js b/browser/main.js index 049802f1ef..ba2c77ebb2 100644 --- a/browser/main.js +++ b/browser/main.js @@ -24,7 +24,6 @@ app.on('activate-with-no-open-windows', function () { }); app.on('ready', function() { - // Create the browser window. var windowOptions = { width: 1200, height: 800,