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/main.less b/app/styles/container-modal.less similarity index 72% rename from app/styles/main.less rename to app/styles/container-modal.less index 6c5055a63a..6fd190d0e7 100644 --- a/app/styles/main.less +++ b/app/styles/container-modal.less @@ -1,60 +1,18 @@ -@import "bootstrap/bootstrap.less"; -@import "clearsans.less"; -@import "theme.less"; -@import "icons.less"; -@import "retina.less"; -@import "setup.less"; -@import "radial.less"; -@import "header.less"; -@import "containers.less"; - -html, body { - height: 100%; - width: 100%; - overflow: hidden; - -webkit-font-smoothing: antialiased; - -webkit-user-select: none; - font-family: 'Clear Sans', sans-serif; - - cursor: default; - img { - pointer-events: none; - } -} - -::-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%; + 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: 610px; + height: 650px; display: flex; + } .modal-body { flex: 1 auto; @@ -68,18 +26,47 @@ html, body { 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 { - a { - color: @gray-lightest; - &:hover { - color: darken(@gray-lightest, 10%); - } - } + color: @gray-lightest; font-size: 10px; text-align: right; } @@ -128,6 +115,7 @@ html, body { .results { overflow: auto; + padding-bottom: 80px; .no-results { text-align: center; @@ -200,6 +188,20 @@ html, body { 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; + } } } } @@ -213,40 +215,3 @@ html, body { opacity: 1; height: 100%; } - -@-webkit-keyframes spin { - 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/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 {