diff --git a/app/ContainerDetails.react.js b/app/ContainerDetails.react.js index a126de1f9c..ab950bf739 100644 --- a/app/ContainerDetails.react.js +++ b/app/ContainerDetails.react.js @@ -161,23 +161,42 @@ var ContainerDetails = React.createClass({ } } + var name = this.props.container.Name; + var image = this.props.container.Config.Image; + var disabledClass = ''; + if (!this.props.container.State.Running) { + disabledClass = 'disabled'; + } + + var buttonClass = React.addons.classSet({ + btn: true, 'btn-action': true, + 'with-icon': true, + disabled: !this.props.container.State.Running + }); + var dropdownButtonClass = React.addons.classSet({ + btn: true, + 'btn-action': true, + 'with-icon': true, + 'dropdown-toggle': true, + disabled: !this.props.container.State.Running + }); + var textButtonClasses = React.addons.classSet({ 'btn': true, 'btn-action': true, 'only-icon': true, - 'active': this.state.page === this.PAGE_LOGS + 'active': this.state.page === this.PAGE_LOGS, + disabled: !this.props.container.State.Running }); var gearButtonClass = React.addons.classSet({ 'btn': true, 'btn-action': true, 'only-icon': true, - 'active': this.state.page === this.PAGE_SETTINGS + 'active': this.state.page === this.PAGE_SETTINGS, + disabled: !this.props.container.State.Running }); - var name = this.props.container.Name; - var image = this.props.container.Config.Image; - return (
@@ -186,16 +205,16 @@ var ContainerDetails = React.createClass({
- View + View
- Volumes + Volumes
- Restart + Restart
- Terminal + Terminal
diff --git a/app/ContainerModal.react.js b/app/ContainerModal.react.js index 383ccf0191..8fff6ddde7 100644 --- a/app/ContainerModal.react.js +++ b/app/ContainerModal.react.js @@ -1,7 +1,13 @@ var async = require('async'); var $ = require('jquery'); +var assign = require('object-assign'); var React = require('react/addons'); -var Modal = require('react-bootstrap/Modal'); +var Modal = require('react-bootstrap').Modal; +var OverlayTrigger = require('react-bootstrap'); +var Popover = require('react-bootstrap/Popover'); +var SplitButton = require('react-bootstrap/SplitButton'); +var MenuItem = require('react-bootstrap/MenuItem'); + var RetinaImage = require('react-retina-image'); var ContainerStore = require('./ContainerStore'); @@ -12,6 +18,8 @@ var ContainerModal = React.createClass({ query: '', results: ContainerStore.recommended(), loading: false, + tags: {}, + active: null, }; }, componentDidMount: function () { @@ -67,11 +75,52 @@ var ContainerModal = React.createClass({ }, 200); } }, - handleClick: function (event) { - var name = event.target.getAttribute('name'); - var self = this; + handleClick: function (name, event) { ContainerStore.create(name, 'latest', function (err, containerName) { - self.props.onRequestHide(); + this.props.onRequestHide(); + }.bind(this)); + }, + handleTagClick: function (tag, name, event) { + ContainerStore.create(name, tag, function (err, containerName) { + this.props.onRequestHide(); + }.bind(this)); + }, + handleDropdownClick: function (name, event) { + this.setState({ + active: name + }); + if (this.state.tags[name]) { + return; + } + $.get('https://registry.hub.docker.com/v1/repositories/' + name + '/tags', function (result) { + var res = {}; + res[name] = result; + console.log(assign(this.state.tags, res)); + this.setState({ + tags: assign(this.state.tags, res) + }); + }.bind(this)); + }, + handleModalClick: function (event) { + if (!this.state.active) { + return; + } + if (!$('.popover').is(event.target)) { + this.setState({ + active: null + }); + } + }, + componentDidUpdate: function () { + if (!this.state.active) { + return; + } + var $dropdown = $(this.getDOMNode()).find('[data-name="' + this.state.active + '"]'); + var $popover = $(this.getDOMNode()).find('.popover'); + + $popover.offset({ + top: $dropdown.offset().top + 32, + left: $dropdown.offset().left - $popover.width() / 2 + 11 }); }, render: function () { @@ -87,6 +136,7 @@ var ContainerModal = React.createClass({ } else { name = {r.name}; } + return (
  • @@ -100,8 +150,11 @@ var ContainerModal = React.createClass({
    - Create - + +
  • @@ -137,26 +190,50 @@ var ContainerModal = React.createClass({ 'search-icon': true }); + var question = ( +
    + An image is a template for a container.}> + What's an image? + +
    + ); + + var tagData = self.state.tags[this.state.active]; + if (tagData) { + var list = tagData.map(function (t) { + return
  • {t.name}
  • ; + }); + tags = ( +
      + {list} +
    + ); + } else { + tags = ; + } + + var popoverClasses = React.addons.classSet({ + popover: true, + hidden: !this.state.active + }); + return ( -
    +
    -
    {title}
    {results}
    - + + {tags} +
    ); diff --git a/app/ContainerStore.js b/app/ContainerStore.js index 05a9c84998..c8701934cd 100644 --- a/app/ContainerStore.js +++ b/app/ContainerStore.js @@ -269,12 +269,13 @@ var ContainerStore = assign(EventEmitter.prototype, { async.map(recommended, function (repository, callback) { $.get('https://registry.hub.docker.com/v1/search?q=' + repository, function (data) { var results = data.results; + console.log(repository, data); callback(null, _.find(results, function (r) { return r.name === repository; })); }); }, function (err, results) { - _recommended = results; + _recommended = results.filter(function(r) { return !!r; }); callback(); }); }, diff --git a/app/Containers.react.js b/app/Containers.react.js index 30a640273b..45ab3f9fd5 100644 --- a/app/Containers.react.js +++ b/app/Containers.react.js @@ -1,6 +1,5 @@ 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'); diff --git a/app/styles/container-modal.less b/app/styles/container-modal.less new file mode 100644 index 0000000000..6fd190d0e7 --- /dev/null +++ b/app/styles/container-modal.less @@ -0,0 +1,217 @@ +.create-modal { + @modal-padding: 32px; + @search-width: 372px; + @custom-width: 270px; + .modal-dialog { + margin-top: 80px; + 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: 650px; + display: flex; + + } + .modal-body { + flex: 1 auto; + display: flex; + flex-direction: row; + padding: 32px 32px; + + aside.custom { + flex: 0 auto; + padding-left: 32px; + min-width: 270px; + } + + .popover { + width: 180px; + text-align: center; + + .popover-content { + max-height: 300px; + padding: 0; + overflow: auto; + } + ul { + padding: 0; + list-style: none; + margin: 0; + li { + padding: 8px 0; + border-bottom: 1px solid #eee; + + &:hover { + color: white; + background: @brand-primary; + } + } + } + .tags-loading { + text-align: center; + margin: 14px auto; + text-align: center; + -webkit-animation-name: spin; + -webkit-animation-duration: 1.8s; + -webkit-animation-iteration-count: infinite; + -webkit-animation-timing-function: linear; + } + } + + section.search { + min-width: 404px; + padding-right: 32px; + border-right: 1px solid #eee; + + .question { + color: @gray-lightest; + font-size: 10px; + text-align: right; + } + + .search-bar { + position: relative; + .loading { + position: absolute; + left: 13px; + top: 10px; + width: 20px; + height: 20px; + -webkit-animation-name: spin; + -webkit-animation-duration: 1.8s; + -webkit-animation-iteration-count: infinite; + -webkit-animation-timing-function: linear; + } + .search-icon { + font-size: 20px; + color: @gray-lighter; + position: absolute; + top: 9px; + left: 14px; + } + input { + border-radius: 20px; + font-size: 13px; + height: 38px; + padding: 8px 16px 8px 40px; + color: @gray-darkest; + margin-bottom: 3px; + border-color: @gray-lightest; + box-shadow: none; + + &:focus { + box-shadow: none; + border-color: @gray-lighter; + } + + &::-webkit-input-placeholder { + color: #ddd; + font-weight: 300; + } + } + } + + .results { + overflow: auto; + padding-bottom: 80px; + + .no-results { + text-align: center; + h3 { + color: #ABC0C0; + font-size: 16px; + margin-top: 160px; + } + } + + .title { + flex: 0 auto; + margin-top: 16px; + } + + ul { + margin-top: 10px; + list-style: none; + padding: 0; + + li { + &:hover { + background-color: lighten(@gray-lightest, 17.5%); + } + display: flex; + flex-direction: row; + padding: 8px 14px 5px 14px; + //margin: 12px; + border-bottom: 1px solid #eee; + &:last-child { + border-bottom: 0; + } + .info { + .name { + color: @gray-darkest; + max-width: 278px; + img { + margin-right: 6px; + margin-left: 2px; + } + font-size: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .properties { + color: @gray-lighter; + 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 { + position: relative; + top: 5px; + text-align: right; + flex: 1 auto; + ul { + text-align: center; + ul { + overflow: auto; + max-height: 300px; + } + } + + + .icon { + position: relative; + top: 2px; + font-size: 11px; + } + } + } + } + } + } + } +} + +.modal-backdrop.in { + background: rgba(227,230,230,0.95); + opacity: 1; + height: 100%; +} diff --git a/app/styles/main.less b/app/styles/main.less index 6c5055a63a..edf89fd2e3 100644 --- a/app/styles/main.less +++ b/app/styles/main.less @@ -1,12 +1,15 @@ @import "bootstrap/bootstrap.less"; +@import "variables.less"; @import "clearsans.less"; @import "theme.less"; @import "icons.less"; +@import "icons-filled.less"; @import "retina.less"; @import "setup.less"; @import "radial.less"; @import "header.less"; @import "containers.less"; +@import "container-modal.less"; html, body { height: 100%; @@ -41,177 +44,14 @@ html, body { 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; - display: flex; - } - .modal-body { - flex: 1 auto; - display: flex; - flex-direction: row; - padding: 32px 32px; +.popover { + font-family: 'Clear Sans', sans-serif; - aside.custom { - flex: 0 auto; - padding-left: 32px; - min-width: 270px; - } - - section.search { - min-width: 404px; - padding-right: 32px; - border-right: 1px solid #eee; - - .question { - a { - color: @gray-lightest; - &:hover { - color: darken(@gray-lightest, 10%); - } - } - font-size: 10px; - text-align: right; - } - - .search-bar { - position: relative; - .loading { - position: absolute; - left: 13px; - top: 10px; - width: 20px; - height: 20px; - -webkit-animation-name: spin; - -webkit-animation-duration: 1.8s; - -webkit-animation-iteration-count: infinite; - -webkit-animation-timing-function: linear; - } - .search-icon { - font-size: 20px; - color: @gray-lighter; - position: absolute; - top: 9px; - left: 14px; - } - input { - border-radius: 20px; - font-size: 13px; - height: 38px; - padding: 8px 16px 8px 40px; - color: @gray-darkest; - margin-bottom: 3px; - border-color: @gray-lightest; - box-shadow: none; - - &:focus { - box-shadow: none; - border-color: @gray-lighter; - } - - &::-webkit-input-placeholder { - color: #ddd; - font-weight: 300; - } - } - } - - .results { - overflow: auto; - - .no-results { - text-align: center; - h3 { - color: #ABC0C0; - font-size: 16px; - margin-top: 160px; - } - } - - .title { - flex: 0 auto; - margin-top: 16px; - } - - ul { - margin-top: 10px; - list-style: none; - padding: 0; - - li { - &:hover { - background-color: lighten(@gray-lightest, 17.5%); - } - display: flex; - flex-direction: row; - padding: 8px 14px 5px 14px; - //margin: 12px; - border-bottom: 1px solid #eee; - &:last-child { - border-bottom: 0; - } - .info { - .name { - color: @gray-darkest; - max-width: 278px; - img { - margin-right: 6px; - margin-left: 2px; - } - font-size: 16px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .properties { - color: @gray-lighter; - 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 { - position: relative; - top: 5px; - text-align: right; - flex: 1 auto; - } - } - } - } - } - } -} - -.modal-backdrop.in { - background: rgba(227,230,230,0.95); - opacity: 1; - height: 100%; + color: @gray-normal; + font-weight: 400; + font-size: 13px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.10); + border: 1px solid #ddd; } @-webkit-keyframes spin { diff --git a/app/styles/theme.less b/app/styles/theme.less index fd87da3215..ac9d02e910 100644 --- a/app/styles/theme.less +++ b/app/styles/theme.less @@ -39,6 +39,7 @@ h4 { color: darken(@btn-color, 10%); cursor: default; box-shadow: none; + background: none; } &:active {