diff --git a/app/ContainerDetails.react.js b/app/ContainerDetails.react.js index c1d48058ab..7ec8d63921 100644 --- a/app/ContainerDetails.react.js +++ b/app/ContainerDetails.react.js @@ -12,12 +12,15 @@ var ContainerUtil = require('./ContainerUtil'); var docker = require('./docker'); var boot2docker = require('./boot2docker'); var ProgressBar = require('react-bootstrap/ProgressBar'); +var Popover = require('react-bootstrap/Popover'); var ContainerDetails = React.createClass({ mixins: [Router.State, Router.Navigation], _oldHeight: 0, PAGE_LOGS: 'logs', PAGE_SETTINGS: 'settings', + PAGE_PORTS: 'ports', + PAGE_VOLUMES: 'volumes', getInitialState: function () { return { logs: [], @@ -77,7 +80,7 @@ var ContainerDetails = React.createClass({ var $viewPopover = $(this.getDOMNode()).find('.popover-view'); var $volumePopover = $(this.getDOMNode()).find('.popover-volume'); - if ($viewDropdown && $volumeDropdown && $viewPopover && $volumePopover) { + /*if ($viewDropdown && $volumeDropdown && $viewPopover && $volumePopover) { $viewPopover.offset({ top: $viewDropdown.offset().top + 32, left: $viewDropdown.offset().left - ($viewPopover.outerWidth() / 2) + 14 @@ -87,7 +90,7 @@ var ContainerDetails = React.createClass({ top: $volumeDropdown.offset().top + 32, left: $volumeDropdown.offset().left + $volumeDropdown.outerWidth() - $volumePopover.outerWidth() / 2 - 20 }); - } + }*/ }, init: function () { var container = ContainerStore.container(this.getParams().name); @@ -127,12 +130,23 @@ var ContainerDetails = React.createClass({ page: this.PAGE_LOGS }); }, + showPorts: function () { + this.setState({ + page: this.PAGE_PORTS + }); + }, + showVolumes: function () { + this.setState({ + page: this.PAGE_VOLUMES + }); + }, showSettings: function () { this.setState({ page: this.PAGE_SETTINGS }); }, handleView: function () { + console.log('CLICKED'); if (this.state.defaultPort) { console.log(this.state.defaultPort); exec(['open', this.state.ports[this.state.defaultPort].url], function (err) { @@ -160,11 +174,6 @@ var ContainerDetails = React.createClass({ console.log(err); }); }, - handleRestart: function () { - ContainerStore.restart(this.props.container.Name, function (err) { - console.log(err); - }); - }, handleTerminal: function () { var container = this.props.container; var terminal = path.join(process.cwd(), 'resources', 'terminal').replace(/ /g, '\\\\ '); @@ -264,13 +273,15 @@ var ContainerDetails = React.createClass({ var state; if (this.props.container.State.Running) { - state =

running

; + state = RUNNING; } else if (this.props.container.State.Restarting) { - state =

restarting

; + state = RESTARTING; } else if (this.props.container.State.Paused) { - state =

paused

; + state = PAUSED; } else if (this.props.container.State.Downloading) { - state =

downloading

; + state = DOWNLOADING; + } else { + state = STOPPED; } var button; @@ -359,6 +370,29 @@ var ContainerDetails = React.createClass({ var dropdownViewButtonClass = React.addons.classSet(assign({'dropdown-view': true}, dropdownClasses)); var dropdownVolumeButtonClass = React.addons.classSet(assign({'dropdown-volume': true}, dropdownClasses)); + var ports = _.map(_.pairs(self.state.ports), function (pair, index, list) { + var key = pair[0]; + var val = pair[1]; + return ( +
+ {key} + {val.display} +
+ ); + }); + + var volumes = _.map(self.props.container.Volumes, function (val, key) { + if (!val || val.indexOf(process.env.HOME) === -1) { + val = 'No Host Folder'; + } + return ( +
+ {key} + {val.replace(process.env.HOME, '~')} +
+ ); + }); + var body; if (this.props.container.State.Downloading) { body = ( @@ -375,106 +409,136 @@ var ContainerDetails = React.createClass({ ); + } else if (this.state.page === this.PAGE_PORTS) { + body = ( +
+
+

Configure Ports

+
+
+
DOCKER PORT
+
MAC PORT
+
+ {ports} +
+
+
+ ); + } else if (this.state.page === this.PAGE_VOLUMES) { + body = ( +
+
+

Configure Volumes

+
+
+
DOCKER FOLDER
+
MAC FOLDER
+
+ {volumes} +
+
+
+ ); } else { body = (
-

Container Name

-
- -
- Save -

Environment Variables

-
-
KEY
-
VALUE
-
-
- {envVars} - {pendingEnvVars} -
- - - +
+

Container Name

+
+
+ Save +
+
+

Environment Variables

+
+
KEY
+
VALUE
+
+
+ {envVars} + {pendingEnvVars} +
+ + + +
+
+ Save +
+
+

Delete Container

+ Delete Container
- Save -

Delete Container

- Delete Container
); } } - var ports = _.map(_.pairs(self.state.ports), function (pair, index, list) { - var key = pair[0]; - var val = pair[1]; - return ( -
- {key} - {val.display} -
- ); + var tabLogsClasses = React.addons.classSet({ + 'tab': true, + 'active': this.state.page === this.PAGE_LOGS, + disabled: this.props.container.State.Downloading }); - var volumes = _.map(self.props.container.Volumes, function (val, key) { - if (!val || val.indexOf(process.env.HOME) === -1) { - val = 'No Host Folder'; - } - return ( -
- {key} - {val.replace(process.env.HOME, '~')} -
- ); + var tabPortsClasses = React.addons.classSet({ + 'tab': true, + 'active': this.state.page === this.PAGE_PORTS, + disabled: this.props.container.State.Downloading + }); + + var tabVolumesClasses = React.addons.classSet({ + 'tab': true, + 'active': this.state.page === this.PAGE_VOLUMES, + disabled: this.props.container.State.Downloading + }); + + var tabSettingsClasses = React.addons.classSet({ + 'tab': true, + 'active': this.state.page === this.PAGE_SETTINGS, + disabled: this.props.container.State.Downloading }); return (
-
-

{this.props.container.Name}

{state}

Image

{this.props.container.Config.Image}

-
+

{this.props.container.Name}

{this.props.container.Config.Image}

-
- View - -
- - - -
- - -
+ + + +
+
+
+ {state} +
+ Logs + Ports + Volumes + Settings
- -
-
-
DOCKER PORT
-
MAC PORT
-
- {ports} -
-
- -
-
-
DOCKER FOLDER
-
MAC FOLDER
-
- {volumes} -
-
{body} + +
+
+
DOCKER PORT
+
MAC PORT
+
+ {ports} +
+
+ +
+
+
DOCKER FOLDER
+
MAC FOLDER
+
+ {volumes} +
+
); } diff --git a/app/ContainerList.react.js b/app/ContainerList.react.js index 0d00fa0e40..5218ae4328 100644 --- a/app/ContainerList.react.js +++ b/app/ContainerList.react.js @@ -7,65 +7,16 @@ var Modal = require('react-bootstrap/Modal'); var RetinaImage = require('react-retina-image'); var ModalTrigger = require('react-bootstrap/ModalTrigger'); var ContainerModal = require('./ContainerModal.react'); +var ContainerListItem = require('./ContainerListItem.react'); var Header = require('./Header.react'); var docker = require('./docker'); var ContainerList = React.createClass({ - componentWillMount: function () { - this._start = Date.now(); - }, render: function () { var self = this; var containers = this.props.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; - - // Synchronize all animations - var style = { - WebkitAnimationDelay: (self._start - Date.now()) + 'ms' - }; - - var state; - 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 =
; - } - return ( - -
  • - {state} -
    -
    - {container.Name} -
    -
    - {imageName} -
    -
    -
  • -
    + ); }); return ( diff --git a/app/ContainerListItem.react.js b/app/ContainerListItem.react.js new file mode 100644 index 0000000000..6ff3251262 --- /dev/null +++ b/app/ContainerListItem.react.js @@ -0,0 +1,84 @@ +var async = require('async'); +var _ = require('underscore'); +var $ = require('jquery'); +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'); +var Header = require('./Header.react'); +var docker = require('./docker'); + +var ContainerListItem = React.createClass({ + componentWillMount: function () { + this._start = Date.now(); + }, + handleItemMouseEnter: function () { + var $action = $(this.getDOMNode()).find('.action'); + $action.show(); + }, + handleItemMouseLeave: function () { + var $action = $(this.getDOMNode()).find('.action'); + $action.hide(); + }, + render: function () { + var self = this; + var container = this.props.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; + + // Synchronize all animations + var style = { + WebkitAnimationDelay: (self._start - Date.now()) + 'ms' + }; + + var state; + 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 =
    ; + } + + return ( + +
  • + {state} +
    +
    + {container.Name} +
    +
    + {imageName} +
    +
    +
    + +
    +
  • +
    + ); + } +}); + +module.exports = ContainerListItem; diff --git a/app/Containers.react.js b/app/Containers.react.js index 0372475515..3377f4fd8a 100644 --- a/app/Containers.react.js +++ b/app/Containers.react.js @@ -79,10 +79,10 @@ var Containers = React.createClass({
    -

    My Containers

    +

    Containers

    }> - +
    diff --git a/app/Header.react.js b/app/Header.react.js index e12d1199f4..30af89c47d 100644 --- a/app/Header.react.js +++ b/app/Header.react.js @@ -40,9 +40,9 @@ var Header = React.createClass({ return (
    -
    -
    -
    +
    +
    +
    ); @@ -50,9 +50,9 @@ var Header = React.createClass({ return (
    -
    -
    -
    +
    +
    +
    ); diff --git a/app/styles/containers.less b/app/styles/containers.less index 4e00a55783..5a99f0eb41 100644 --- a/app/styles/containers.less +++ b/app/styles/containers.less @@ -13,99 +13,6 @@ flex-direction: column; padding: 14px 14px 20px; - .table { - margin-bottom: 0; - .icon-arrow-right { - color: #aaa; - margin: 2px 9px 0; - flex: 0 auto; - min-width: 13px; - } - .btn { - min-width: 22px; - margin-left: 10px; - } - .table-labels { - flex: 1 auto; - display: flex; - font-size: 12px; - color: @gray-lightest; - .label-left { - flex: 0 auto; - min-width: 80px; - margin-right: 30px; - text-align: right; - } - .label-right { - flex: 1 auto; - display: inline-block; - width: 40%; - } - } - .table-values { - flex: 1 auto; - display: flex; - flex-direction: row; - margin: 8px 0; - .value-left { - text-align: right; - min-width: 80px; - flex: 0 auto; - } - .value-right { - flex: 1 auto; - -webkit-user-select: text; - width: 154px; - } - } - .table-new { - margin-top: 10px; - flex: 1 auto; - display: flex; - input { - padding: 0; - font-weight: 400; - } - input.new-left { - flex: 0 auto; - text-align: right; - min-width: 80px; - max-width: 80px; - } - .new-right-wrapper { - position: relative; - display: flex; - flex: 1 auto; - .new-right-placeholder { - position: absolute; - top: 3px; - left: 0; - font-weight: 400; - } - - input.new-right { - flex: 1 auto; - height: 24px; - position :relative; - padding-left: 107px; - } - } - } - - &.volumes { - .label-left { - min-width: 120px; - } - .value-left { - min-width: 120px; - } - .icon { - color: #aaa; - margin: 2px 9px 0; - } - } - } - .question { margin: 12px 6px 6px; } @@ -122,22 +29,26 @@ display: flex; flex-direction: row; + padding: 0px; + .sidebar { + padding-top: 28px; + background-color: white; display: flex; flex-direction: column; - min-width: 280px; + min-width: 260px; margin: 0; box-sizing: border-box; - border-right: 1px solid #eee; + border-right: 1px solid #DCE2E2; .sidebar-header { flex: 0 auto; min-width: 240px; - min-height: 42px; + min-height: 47px; display: flex; border-bottom: 1px solid transparent; transition: border-bottom 0.25s; - padding: 0px 10px 0px 10px; + //padding: 0px 10px 0px 10px; &.sep { border-bottom: 1px solid #eee; @@ -146,27 +57,27 @@ h4 { align-self: flex-start; - padding: 0 24px; + //padding: 0 24px; + padding-left: 26px; margin: 14px 0 0; display: inline-block; - font-size: 14px; position: relative; } .create { flex: 1 auto; text-align: right; - /*.btn { - margin-top: 4px; - padding: 4px 7px; - font-size: 16px; - position: relative; - .icon { - position: relative; - top: 3px; - left: 1px; + margin-right: 20px; + margin-top: 3px; + + .btn-new { + font-size: 24px; + color: @brand-action; + transition: all 0.25s; + &:hover { + color: darken(@brand-action, 10%); } - }*/ + } } } @@ -176,7 +87,7 @@ overflow-y: scroll; overflow-x: hidden; box-sizing: border-box; - max-width: 280px; + max-width: 260px; &.sep { border-top: 1px solid #eee; @@ -184,9 +95,7 @@ ul { margin: 0; - min-width: 240px; padding: 0; - margin-top: 4px; display: flex; flex-direction: column; @@ -195,23 +104,29 @@ color: inherit; flex-shrink: 0; cursor: default; - margin: 0px 3px 0px 8px; outline: none; - padding: 4px 5px; &.active { li { border-bottom: none; - border-radius: 40px; - background: @brand-primary; + background-image: linear-gradient(-180deg, #24B8EB 4%, #24A3EB 100%); .name { color: white; } .image { color: white; - opacity: 0.9; + opacity: 0.8; + } + .btn-delete { + font-size: 24px; + color: white; + opacity: 0.8; + transition: all 0.25s; + &:hover { + opacity: 1; + color: white; + } } - .state-running { .at2x('running-white.png', 20px, 20px); @@ -244,7 +159,7 @@ li { vertical-align: middle; - padding: 10px 16px 10px 16px; + padding: 10px 16px 10px 26px; display: flex; flex-direction: row; @@ -271,6 +186,23 @@ } } + .action { + display: none; + flex: 1; + position: relative; + top: 5px; + text-align: right; + margin-right: 4px; + .btn-delete { + font-size: 24px; + color: @gray-lighter; + transition: all 0.25s; + &:hover { + color: @brand-action; + } + } + } + .state { margin-top: 9px; display: inline-block; @@ -370,16 +302,108 @@ display: flex; flex-direction: column; + .details-subheader { + flex: 0 auto; + display: flex; + flex-direction: row; + position: relative; + border-bottom: 1px solid @gray-lightest; + background-color: white; + height: 32px; + padding: 8px 10px 10px 24px; + font-size: 12px; + color: @gray-normal; + .status { + font-weight: 500; + position: relative; + top: -2px; + &.running { + color: @brand-positive; + } + &.paused { + color: @gray-lighter; + } + &.stopped { + color: @gray-lighter; + } + } + .details-subheader-tabs { + flex: 1 auto; + text-align: right; + margin-right: 0px; + .tab { + margin-left: 20px; + padding: 3px 10px; + transition: all 0.25s; + &:hover { + color: @brand-action; + } + &.active { + background-color: lighten(@brand-action, 38%); + border-radius: 4px; + color: darken(@brand-action, 25%); + } + } + } + } + .details-header { flex: 0 auto; display: flex; - flex-direction: column; - padding: 4px 40px 10px 40px; + flex-direction: row; + padding: 31px 24px 18px 24px; position: relative; - border-bottom: 1px solid #eee; + border-bottom: 1px solid #DCE2E2; + background-color: #F9F9f9; + height: 75px; + h1 { + margin: 0; + font-size: 20px; + font-weight: 300; + margin: 0; + color: @gray-darkest; + } + h2 { + &.status { + margin: 9px 0px 0px 12px; + font-weight: bold; + font-size: 10px; + &.running { + color: @brand-positive; + } + } + &.image { + flex: 1 auto; + margin: 7px 0px 0px 16px; + font-size: 12px; + color: @gray-normal; + font-weight: 300; + } + } .details-header-actions { - flex: 0 auto; + flex: 1 auto; + text-align: right; + margin-top: -6px; + .action-icon { + font-size: 23px; + margin-left: 24px; + color: @gray-darker; + transition: all 0.25s; + &:hover { + color: @brand-action; + } + &.view-icon { + position: relative; + top: 2px; + font-size: 27px; + //color: @gray-darkest; + } + } + } + + /*.details-header-actions { + flex: 1; display: flex; flex-direction: row; margin-top: 24px; @@ -400,44 +424,7 @@ z-index: 0; } } - } - - .details-header-info { - display: flex; - flex-direction: row; - a { - position: absolute; - right: 30px; - top: -4px; - } - h1 { - margin: 0; - font-size: 20px; - margin: 0; - color: @gray-darkest; - } - h2 { - &.status { - margin: 8px 0px 0px 16px; - text-transform: uppercase; - font-weight: bold; - font-size: 10px; - &.running { - color: @brand-positive; - } - } - &.image-label { - margin: 8px 0px 0px 30px; - font-size: 10px; - color: @gray-lighter; - } - &.image { - margin: 5px 0px 0px 16px; - font-size: 14px; - color: @gray-normal; - } - } - } + }*/ } .details-progress { @@ -449,10 +436,11 @@ flex: 1; overflow: auto; .logs { + background-color: #FEFEFE; -webkit-user-select: text; font-family: Menlo; font-size: 12px; - padding: 18px 35px; + padding: 20px 20px; color: lighten(@gray-normal, 6%); white-space: pre-wrap; p { @@ -460,7 +448,110 @@ } } .settings { - padding: 18px 35px; + padding: 18px 38px; + .settings-section { + margin-bottom: 40px; + } + } + .ports { + padding: 18px 38px; + } + .volumes { + padding: 18px 38px; + } + + .table { + margin-bottom: 0; + .icon-arrow-right { + color: #aaa; + margin: 2px 9px 0; + flex: 0 auto; + min-width: 13px; + } + .btn { + min-width: 22px; + margin-left: 10px; + } + .table-labels { + margin-top: 20px; + flex: 1 auto; + display: flex; + font-size: 12px; + color: @gray-lightest; + .label-left { + flex: 0 auto; + min-width: 80px; + margin-right: 30px; + text-align: right; + } + .label-right { + flex: 1 auto; + display: inline-block; + width: 40%; + } + } + .table-values { + flex: 1 auto; + display: flex; + flex-direction: row; + margin: 8px 0; + .value-left { + text-align: right; + min-width: 80px; + flex: 0 auto; + } + .value-right { + flex: 1 auto; + -webkit-user-select: text; + width: 40%; + } + } + .table-new { + margin-top: 10px; + flex: 1 auto; + display: flex; + input { + padding: 0; + font-weight: 400; + } + input.new-left { + flex: 0 auto; + text-align: right; + min-width: 80px; + max-width: 80px; + } + .new-right-wrapper { + position: relative; + display: flex; + flex: 1 auto; + .new-right-placeholder { + position: absolute; + top: 3px; + left: 0; + font-weight: 400; + } + + input.new-right { + flex: 1 auto; + height: 24px; + position :relative; + padding-left: 107px; + } + } + } + + &.volumes { + .label-left { + min-width: 120px; + } + .value-left { + min-width: 120px; + } + .icon { + color: #aaa; + margin: 2px 9px 0; + } + } } } @@ -477,6 +568,7 @@ color: @gray-lightest; margin-left: 5px; margin-bottom: 5px; + margin-top: 20px; .label-key { display: inline-block; margin-right: 30px; diff --git a/app/styles/header.less b/app/styles/header.less index 4731611def..a55c503aab 100644 --- a/app/styles/header.less +++ b/app/styles/header.less @@ -1,9 +1,10 @@ @import "bootstrap/bootstrap.less"; .header { + position: absolute; min-width: 100%; flex: 0; - min-height: 50px; + min-height: 30px; -webkit-app-region: drag; -webkit-user-select: none; // border-bottom: 1px solid #efefef; @@ -15,8 +16,8 @@ .buttons { display: inline-block; position: relative; - top: 16px; - left: 20px; + top: 10px; + left: 15px; &:hover { .button-minimize.enabled { @@ -44,7 +45,18 @@ border-radius: 6px; box-shadow: 0px 1px 1px 0px rgba(234,234,234,0.50); -webkit-app-region: no-drag; - + &.red { + background-color: #FF5F52; + border-color: #E33E32; + } + &.yellow { + background-color: #FFBE05; + border-color: #E2A100; + } + &.green { + background-color: #15CC35; + border-color: #17B230; + } &.disabled { border: 1px solid #E8EEEF; } diff --git a/app/styles/main.less b/app/styles/main.less index 81fe584909..c852cd8fc8 100644 --- a/app/styles/main.less +++ b/app/styles/main.less @@ -17,7 +17,7 @@ html, body { overflow: hidden; -webkit-font-smoothing: antialiased; -webkit-user-select: none; - font-family: 'Clear Sans', sans-serif; + font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; cursor: default; img { @@ -25,7 +25,7 @@ html, body { } } -::-webkit-scrollbar { +/*::-webkit-scrollbar { width: 13px; } @@ -46,7 +46,7 @@ html, body { &:hover { background-color: rgba(0,0,0,0.25); } -} +}*/ .question { color: @gray-lightest; diff --git a/app/styles/theme.less b/app/styles/theme.less index faa71737d8..24bfbcac50 100644 --- a/app/styles/theme.less +++ b/app/styles/theme.less @@ -12,7 +12,7 @@ h3 { h4 { font-size: 13px; - color: @gray-normal; + color: @gray-darker; font-weight: 400; } @@ -28,7 +28,7 @@ input[type="text"] { color: @gray-normal; font-weight: 300; padding: 5px; - transition: all 0.1s; + transition: all 0.25s; &:focus { outline: 0; border-bottom: 1px solid @brand-action; @@ -56,7 +56,7 @@ input[type="text"] { // Mixin for generating new styles .btn-styles(@btn-color: @gray-normal) { - transition: all 0.1s; + transition: all 0.25s; .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners border-color: @btn-color; color: @btn-color; @@ -111,7 +111,7 @@ input[type="text"] { // Common styles .btn { - font-size: 12px; + font-size: 13px; background-color: transparent; color: @gray-normal; border: 1px solid @gray-normal; @@ -119,8 +119,8 @@ input[type="text"] { box-shadow: none; font-weight: 400; text-shadow: none; - padding: 6px 14px 6px 14px; - height: 32px; + padding: 4px 14px 4px 14px; + height: 28px; cursor: default; &.small { @@ -166,7 +166,7 @@ input[type="text"] { &.only-icon { padding: 6px 7px 6px 7px; &.small { - padding: 2px 5px 3px 5px; + padding: 3px 5px 3px 5px; } } } diff --git a/app/styles/variables.less b/app/styles/variables.less index cff01eb030..3d8a1b7f92 100644 --- a/app/styles/variables.less +++ b/app/styles/variables.less @@ -1,6 +1,6 @@ @brand-primary: #24B8EB; @brand-action: #24B8EB; -@brand-positive: #65E100; +@brand-positive: #16E568; @brand-negative: #F47A45; @gray-darkest: #253237;