From f34e23b7a0a8c28f54dd9149aab100df77b75d90 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 19 Jan 2015 12:19:40 -0500 Subject: [PATCH] Change unique container key to name from Id --- app/ContainerDetails.react.js | 57 +++- app/ContainerModal.react.js | 6 +- app/ContainerStore.js | 21 +- app/Containers.react.js | 43 +-- app/main.js | 36 +-- app/styles/{main.less => containers.less} | 309 ++-------------------- app/styles/header.less | 61 +++++ app/styles/theme.less | 33 +-- 8 files changed, 197 insertions(+), 369 deletions(-) rename app/styles/{main.less => containers.less} (51%) create mode 100644 app/styles/header.less diff --git a/app/ContainerDetails.react.js b/app/ContainerDetails.react.js index 63c0e733..b8c93ae5 100644 --- a/app/ContainerDetails.react.js +++ b/app/ContainerDetails.react.js @@ -8,8 +8,8 @@ var Link = Router.Link; var RouteHandler = Router.RouteHandler; var Convert = require('ansi-to-html'); var convert = new Convert(); -var ContainerStore = require('./ContainerStore.js'); -var docker = require('./docker.js'); +var ContainerStore = require('./ContainerStore'); +var docker = require('./docker'); var ContainerDetails = React.createClass({ mixins: [Router.State], @@ -18,12 +18,12 @@ var ContainerDetails = React.createClass({ logs: [] }; }, - componentWillMount: function () { + componentWillReceiveProps: function () { this.update(); var self = this; var logs = []; var index = 0; - docker.client().getContainer(this.getParams().Id).logs({ + docker.client().getContainer(this.getParams().name).logs({ follow: false, stdout: true, timestamps: true @@ -40,7 +40,50 @@ var ContainerDetails = React.createClass({ }); stream.on('end', function (buf) { self.setState({logs: logs}); - docker.client().getContainer(self.getParams().Id).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; + }); + }); + }); + }); + }, + componentWillMount: function () { + 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, @@ -68,9 +111,9 @@ var ContainerDetails = React.createClass({ ContainerStore.removeChangeListener(this.update); }, update: function () { - var containerId = this.getParams().Id; + var containerName = this.getParams().name; this.setState({ - container: ContainerStore.containers()[containerId] + container: ContainerStore.containers()[containerName] }); }, _escapeHTML: function (html) { diff --git a/app/ContainerModal.react.js b/app/ContainerModal.react.js index aa37a2e8..9da1b4e8 100644 --- a/app/ContainerModal.react.js +++ b/app/ContainerModal.react.js @@ -3,7 +3,7 @@ var Router = require('react-router'); var Modal = require('react-bootstrap/Modal'); var RetinaImage = require('react-retina-image'); var $ = require('jquery'); -var ContainerStore = require('./ContainerStore.js'); +var ContainerStore = require('./ContainerStore'); var ContainerModal = React.createClass({ getInitialState: function () { @@ -28,7 +28,6 @@ var ContainerModal = React.createClass({ if (query === this.state.query) { return; } - clearTimeout(this.timeout); var self = this; this.timeout = setTimeout(function () { @@ -37,7 +36,8 @@ var ContainerModal = React.createClass({ }, handleClick: function (event) { var name = event.target.getAttribute('name'); - ContainerStore.create(name); + ContainerStore.create(name, 'latest', function (err, containerName) { + }); }, render: function () { var top = this.state.results.splice(0, 7); diff --git a/app/ContainerStore.js b/app/ContainerStore.js index 3c0dc93e..3e0fb0e0 100644 --- a/app/ContainerStore.js +++ b/app/ContainerStore.js @@ -42,6 +42,7 @@ var ContainerStore = assign(EventEmitter.prototype, { docker.client().getEvents(function (err, stream) { stream.setEncoding('utf8'); stream.on('data', function (data) { + console.log(data); // TODO: Make self.update(function (err) { @@ -69,10 +70,9 @@ var ContainerStore = assign(EventEmitter.prototype, { } var containers = {}; results.map(function (r) { - containers[r.Id] = r; + containers[r.Name.replace('/', '')] = r; }); self._containers = containers; - console.log(containers); self.emit('change'); callback(null); }); @@ -157,6 +157,10 @@ var ContainerStore = assign(EventEmitter.prototype, { } } }, + // Returns all shoes + containers: function() { + return this._containers; + }, create: function (repository, tag, callback) { console.log('create', repository, tag); @@ -182,6 +186,8 @@ var ContainerStore = assign(EventEmitter.prototype, { } console.log('Placeholder container created.'); docker.client().pull(imageName, function (err, stream) { + console.log(containerName); + callback(null, containerName); stream.setEncoding('utf8'); stream.on('data', function (data) { console.log(data); @@ -197,6 +203,7 @@ var ContainerStore = assign(EventEmitter.prototype, { } else { // If not then directly create the container self._createContainer(imageName, containerName, function () { + callback(null, containerName); console.log('done'); }); } @@ -205,19 +212,15 @@ var ContainerStore = assign(EventEmitter.prototype, { // Pull image // When image is done pulling then }, - - // Returns all shoes - containers: function() { - return this._containers; + logs: function (containerName) { + return logs[containerId]; }, - addChangeListener: function(callback) { this.on('change', callback); }, - removeChangeListener: function(callback) { this.removeListener('change', callback); - } + }, }); module.exports = ContainerStore; diff --git a/app/Containers.react.js b/app/Containers.react.js index c9a562e4..b5df11c4 100644 --- a/app/Containers.react.js +++ b/app/Containers.react.js @@ -1,20 +1,18 @@ -var React = require('react'); +var React = require('react/addons'); var Router = require('react-router'); var Modal = require('react-bootstrap/Modal'); var RetinaImage = require('react-retina-image'); var ModalTrigger = require('react-bootstrap/ModalTrigger'); -var ContainerModal = require('./ContainerModal.react.js'); -var ContainerStore = require('./ContainerStore.js'); -var Route = Router.Route; -var NotFoundRoute = Router.NotFoundRoute; -var DefaultRoute = Router.DefaultRoute; +var ContainerModal = require('./ContainerModal.react'); +var ContainerStore = require('./ContainerStore'); +var Header = require('./Header.react'); +var async = require('async'); +var _ = require('underscore'); +var docker = require('./docker'); + var Link = Router.Link; var RouteHandler = Router.RouteHandler; var Navigation= Router.Navigation; -var Header = require('./Header.react.js'); -var async = require('async'); -var _ = require('underscore'); -var docker = require('./docker.js'); var ContainerList = React.createClass({ mixins: [Navigation], @@ -22,15 +20,12 @@ var ContainerList = React.createClass({ return { containers: [] }; - }, - handleClick: function () { - }, componentDidMount: function () { this.update(); ContainerStore.addChangeListener(this.update); - if (this.state.containers.length > 0) { - this.transitionTo('container', {Id: this.state.containers[0].Id}); + if (this.state.active) { + this.transitionTo('container', {name: this.state.active}); } }, componentWillMount: function () { @@ -43,8 +38,16 @@ var ContainerList = React.createClass({ var containers = _.values(ContainerStore.containers()).sort(function (a, b) { return a.Name.localeCompare(b.Name); }); + var state = {}; + if (!this.state.active && containers.length > 0) { + state.active = containers[0].Name.replace('/', ''); + } + state.containers = containers; + this.setState(state); + }, + handleClick: function (containerId) { this.setState({ - containers: containers + active: containerId }); }, render: function () { @@ -85,8 +88,9 @@ var ContainerList = React.createClass({ } else { state =
; } + return ( - +
  • {state}
    @@ -126,9 +130,6 @@ var Containers = React.createClass({ }); } }, - handleClick: function () { - ContainerStore.create('dockerfile/ghost', 'latest', 'testghost'); - }, render: function () { var sidebarHeaderClass = 'sidebar-header'; if (this.state.sidebarOffset) { @@ -140,7 +141,7 @@ var Containers = React.createClass({
    -

    containers

    +

    containers

    }>
    diff --git a/app/main.js b/app/main.js index 11d5f92e..0e6a4bfd 100644 --- a/app/main.js +++ b/app/main.js @@ -1,12 +1,6 @@ var React = require('react'); var Router = require('react-router'); var RetinaImage = require('react-retina-image'); -var Route = Router.Route; -var NotFoundRoute = Router.NotFoundRoute; -var DefaultRoute = Router.DefaultRoute; -var Link = Router.Link; -var RouteHandler = Router.RouteHandler; - var Raven = require('raven'); var async = require('async'); var docker = require('./docker.js'); @@ -15,7 +9,13 @@ var Setup = require('./Setup.react'); var Containers = require('./Containers.react'); var ContainerDetails = require('./ContainerDetails.react'); var ContainerStore = require('./ContainerStore.js'); -var Radial = require('./Radial.react'); +var Radial = require('./Radial.react.js'); + +var Route = Router.Route; +var NotFoundRoute = Router.NotFoundRoute; +var DefaultRoute = Router.DefaultRoute; +var Link = Router.Link; +var RouteHandler = Router.RouteHandler; var NoContainers = React.createClass({ render: function () { @@ -38,7 +38,7 @@ var App = React.createClass({ var routes = ( - + @@ -48,17 +48,19 @@ var routes = ( ); -Router.run(routes, function (Handler) { - boot2docker.ip(function (err, ip) { - if (!err) { - docker.setHost(ip); - ContainerStore.init(function () { - React.render(, document.body); +boot2docker.ip(function (err, ip) { + if (!err) { + docker.setHost(ip); + ContainerStore.init(function () { + Router.run(routes, function (Handler) { + React.render(, document.body); }); - } else { + }); + } else { + Router.run(routes, function (Handler) { React.render(, document.body); - } - }); + }); + } }); if (process.env.NODE_ENV !== 'development') { diff --git a/app/styles/main.less b/app/styles/containers.less similarity index 51% rename from app/styles/main.less rename to app/styles/containers.less index d2e3fe94..b1cc65c5 100644 --- a/app/styles/main.less +++ b/app/styles/containers.less @@ -1,71 +1,3 @@ -@import "bootstrap/bootstrap.less"; -@import "clearsans.less"; -@import "theme.less"; -@import "icons.less"; -@import "retina.less"; -@import "setup.less"; -@import "radial.less"; - -.buttons { - display: inline-block; - position: relative; - top: 16px; - left: 20px; - - &:hover { - .button-minimize.enabled { - .at2x('minimize.png', 10px, 10px); - } - .button-close.enabled { - .at2x('close.png', 10px, 10px); - } - .button-fullscreen.enabled { - .at2x('fullscreen.png', 10px, 10px); - } - .button-fullscreenclose.enabled { - .at2x('fullscreenclose.png', 10px, 10px); - } - } - - .button { - box-sizing: border-box; - display: inline-block; - background: white; - margin-right: 9px; - height: 12px; - width: 12px; - border: 1px solid #CCD3D5; - border-radius: 6px; - box-shadow: 0px 1px 1px 0px rgba(234,234,234,0.50); - -webkit-app-region: no-drag; - - &.disabled { - border: 1px solid #E8EEEF; - } - - &.enabled:hover { - box-shadow: 0px 1px 1px 0px rgba(195,198,201,0.50); - } - - &.enabled:hover:active { - cursor: default; - -webkit-filter: brightness(92%); - } - } -} - -.header { - min-width: 100%; - flex: 0; - min-height: 48px; - -webkit-app-region: drag; - -webkit-user-select: none; - - &.no-drag { - -webkit-app-region: no-drag; - } -} - .containers { height: 100%; display: flex; @@ -78,20 +10,21 @@ .sidebar { display: flex; flex-direction: column; - min-width: 240px; + min-width: 260px; margin: 0; box-sizing: border-box; border-right: 1px solid #eee; .sidebar-header { flex: 0 auto; - min-width: 240px; + min-width: 260px; display: flex; border-bottom: 1px solid transparent; transition: border-bottom 0.25s; &.sep { border-bottom: 1px solid #eee; + box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.03); } h3 { @@ -148,7 +81,7 @@ ul { padding: 0; margin: 0; - min-width: 240px; + min-width: 260px; position: absolute; top: 0; bottom: 0; @@ -163,23 +96,18 @@ flex-shrink: 0; cursor: default; - /*&:hover { + &.active { + background: #eee; + border-bottom: none; + + &:hover { + } + } + + &:hover { text-decoration: none; cursor: default; - background: @brand-primary; - - li { - border-bottom: none; - } - - li > .info > .name { - color: #fff; - } - - li > .info > .image { - color: #fff; - } - }*/ + } &:focus { text-decoration: none; } @@ -187,10 +115,8 @@ li { vertical-align: middle; - - margin: 14px 24px 0px; - border-bottom: 1px solid #efefef; padding-bottom: 14px; + margin: 16px 24px 0px; display: flex; flex-direction: row; @@ -198,18 +124,18 @@ .info { font-size: 13px; - margin-left: 12px; + margin-left: 16px; .name { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; - font-size: 13px; + font-size: 14px; font-weight: 400; color: #555; } .image { color: #999; - font-size: 10px; + font-size: 12px; font-weight: 400; text-overflow: ellipsis; white-space: nowrap; @@ -218,7 +144,7 @@ } .state { - margin-top: 8px; + margin-top: 9px; display: inline-block; position: relative; min-width: 20px; @@ -349,200 +275,3 @@ } } } - -html, body { - height: 100%; - width: 100%; - overflow: hidden; - -webkit-font-smoothing: antialiased; - user-select: none; - font-family: 'Clear Sans', sans-serif; -} - -::-webkit-scrollbar { - width: 13px; -} - -::-webkit-scrollbar-track { - margin: 3px; - -webkit-border-radius: 5px; - border-radius: 5px; - background: none; -} - -::-webkit-scrollbar-thumb { - border: 3px solid rgba(0, 0, 0, 0); - background-clip: padding-box; - width: 7px; - border-radius: 8px; - background-color: rgba(0,0,0,0.2); -} - -.create-modal { - @modal-padding: 32px; - @search-width: 372px; - @custom-width: 270px; - .modal-dialog { - margin-top: 8%; - width: calc(@modal-padding + @search-width + 2 * @modal-padding + @custom-width); - } - .modal-content { - //box-shadow: 0 3px 15px rgba(0, 0, 0, 0.2); - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.10); - border: none; //1px solid #ccc; - height: 610px; - } - .modal-body { - display: flex; - flex-direction: row; - padding: 32px 32px; - - .title { - color: #CCD3D5; - font-weight: 400; - font-size: 13px; - } - - aside.custom { - flex: 0 auto; - padding-left: 32px; - min-width: 270px; - } - - section.search { - flex: 0 auto; - min-width: 404px; - padding-right: 32px; - border-right: 1px solid #eee; - - .question { - a { - color: #CCD3D5; - } - font-size: 10px; - text-align: right; - } - - input { - border-radius: 20px; - font-size: 13px; - height: 38px; - padding: 8px 16px; - font-weight: 400; - color: #666; - - &:focus { - box-shadow: none; - border-color: #bbb; - } - - &::-webkit-input-placeholder { - color: #ddd; - font-weight: 300; - } - } - - .results { - overflow: auto; - - .title { - margin-top: 16px; - } - - ul { - list-style: none; - color: #555; - padding: 0; - - li { - display: flex; - flex-direction: row; - margin: 12px; - border-bottom: 1px solid #eee; - .info { - .name { - max-width: 278px; - img { - margin-right: 6px; - margin-left: 2px; - } - font-size: 16px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .stars { - color: #A7A7A7; - margin-top: 2px; - - .star-count { - font-size: 10px; - display: inline-block; - position: relative; - top: -3px; - left: 1px; - height: 17px; - } - - .icon { - overflow: hidden; - display: inline-block; - font-size: 15px; - height: 15px; - } - } - flex: 0 auto; - } - .action { - text-align: right; - flex: 1 auto; - } - } - } - } - } - } -} - -.modal-backdrop.in { - background: rgba(227,230,230,0.95); - opacity: 1; - height: 100%; -} - -@-webkit-keyframes translatedownload { - from { - -webkit-transform: rotate(0deg); - } - to { - -webkit-transform: rotate(360deg); - } -} - -@-webkit-keyframes translatewave { - from { - -webkit-transform: translateX(0px); - } - to { - -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/app/styles/header.less b/app/styles/header.less new file mode 100644 index 00000000..63f8f0d9 --- /dev/null +++ b/app/styles/header.less @@ -0,0 +1,61 @@ +@import "bootstrap/bootstrap.less"; + +.header { + min-width: 100%; + flex: 0; + min-height: 48px; + -webkit-app-region: drag; + -webkit-user-select: none; + + &.no-drag { + -webkit-app-region: no-drag; + } + + .buttons { + display: inline-block; + position: relative; + top: 16px; + left: 20px; + + &:hover { + .button-minimize.enabled { + .at2x('minimize.png', 10px, 10px); + } + .button-close.enabled { + .at2x('close.png', 10px, 10px); + } + .button-fullscreen.enabled { + .at2x('fullscreen.png', 10px, 10px); + } + .button-fullscreenclose.enabled { + .at2x('fullscreenclose.png', 10px, 10px); + } + } + + .button { + box-sizing: border-box; + display: inline-block; + background: white; + margin-right: 9px; + height: 12px; + width: 12px; + border: 1px solid #CCD3D5; + border-radius: 6px; + box-shadow: 0px 1px 1px 0px rgba(234,234,234,0.50); + -webkit-app-region: no-drag; + + &.disabled { + border: 1px solid #E8EEEF; + } + + &.enabled:hover { + box-shadow: 0px 1px 1px 0px rgba(195,198,201,0.50); + } + + &.enabled:hover:active { + cursor: default; + -webkit-filter: brightness(92%); + } + } + } +} diff --git a/app/styles/theme.less b/app/styles/theme.less index de7f6581..9d80a819 100644 --- a/app/styles/theme.less +++ b/app/styles/theme.less @@ -17,24 +17,11 @@ .btn-info, .btn-warning, .btn-danger { - text-shadow: 0 -1px 0 rgba(0,0,0,.2); - @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075); - .box-shadow(@shadow); - // Reset the shadow - &:active, - &.active { - .box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); - } - - .badge { - text-shadow: none; - } } // Mixin for generating new styles .btn-styles(@btn-color: #555) { - #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%)); .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners background-repeat: repeat-x; border-color: darken(@btn-color, 14%); @@ -42,33 +29,35 @@ &:hover, &:focus { background-color: darken(@btn-color, 12%); - background-position: 0 -15px; - } - - &:active, - &.active { - background-color: darken(@btn-color, 12%); - border-color: darken(@btn-color, 14%); } &:disabled, &[disabled] { background-color: darken(@btn-color, 12%); - background-image: none; } } // Common styles .btn { + border-radius: 25px; + box-shadow: none; + font-weight: 400; + text-shadow: none; // Remove the gradient for the pressed/active state &:active, &.active { background-image: none; } + + &:focus, + &.focus { + box-shadow: none; + outline: none !important; + } } // Apply the mixin to the buttons -.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; } +.btn-default { .btn-styles(@btn-default-bg); } .btn-primary { .btn-styles(@btn-primary-bg); } .btn-success { .btn-styles(@btn-success-bg); } .btn-info { .btn-styles(@btn-info-bg); }