Refactored container list. Hover over effects. Frontend restyle.

This commit is contained in:
Sean Li 2015-01-30 12:11:41 -08:00
parent e00454f1ba
commit 736e277fd2
10 changed files with 528 additions and 325 deletions

View File

@ -12,12 +12,15 @@ var ContainerUtil = require('./ContainerUtil');
var docker = require('./docker'); var docker = require('./docker');
var boot2docker = require('./boot2docker'); var boot2docker = require('./boot2docker');
var ProgressBar = require('react-bootstrap/ProgressBar'); var ProgressBar = require('react-bootstrap/ProgressBar');
var Popover = require('react-bootstrap/Popover');
var ContainerDetails = React.createClass({ var ContainerDetails = React.createClass({
mixins: [Router.State, Router.Navigation], mixins: [Router.State, Router.Navigation],
_oldHeight: 0, _oldHeight: 0,
PAGE_LOGS: 'logs', PAGE_LOGS: 'logs',
PAGE_SETTINGS: 'settings', PAGE_SETTINGS: 'settings',
PAGE_PORTS: 'ports',
PAGE_VOLUMES: 'volumes',
getInitialState: function () { getInitialState: function () {
return { return {
logs: [], logs: [],
@ -77,7 +80,7 @@ var ContainerDetails = React.createClass({
var $viewPopover = $(this.getDOMNode()).find('.popover-view'); var $viewPopover = $(this.getDOMNode()).find('.popover-view');
var $volumePopover = $(this.getDOMNode()).find('.popover-volume'); var $volumePopover = $(this.getDOMNode()).find('.popover-volume');
if ($viewDropdown && $volumeDropdown && $viewPopover && $volumePopover) { /*if ($viewDropdown && $volumeDropdown && $viewPopover && $volumePopover) {
$viewPopover.offset({ $viewPopover.offset({
top: $viewDropdown.offset().top + 32, top: $viewDropdown.offset().top + 32,
left: $viewDropdown.offset().left - ($viewPopover.outerWidth() / 2) + 14 left: $viewDropdown.offset().left - ($viewPopover.outerWidth() / 2) + 14
@ -87,7 +90,7 @@ var ContainerDetails = React.createClass({
top: $volumeDropdown.offset().top + 32, top: $volumeDropdown.offset().top + 32,
left: $volumeDropdown.offset().left + $volumeDropdown.outerWidth() - $volumePopover.outerWidth() / 2 - 20 left: $volumeDropdown.offset().left + $volumeDropdown.outerWidth() - $volumePopover.outerWidth() / 2 - 20
}); });
} }*/
}, },
init: function () { init: function () {
var container = ContainerStore.container(this.getParams().name); var container = ContainerStore.container(this.getParams().name);
@ -127,12 +130,23 @@ var ContainerDetails = React.createClass({
page: this.PAGE_LOGS page: this.PAGE_LOGS
}); });
}, },
showPorts: function () {
this.setState({
page: this.PAGE_PORTS
});
},
showVolumes: function () {
this.setState({
page: this.PAGE_VOLUMES
});
},
showSettings: function () { showSettings: function () {
this.setState({ this.setState({
page: this.PAGE_SETTINGS page: this.PAGE_SETTINGS
}); });
}, },
handleView: function () { handleView: function () {
console.log('CLICKED');
if (this.state.defaultPort) { if (this.state.defaultPort) {
console.log(this.state.defaultPort); console.log(this.state.defaultPort);
exec(['open', this.state.ports[this.state.defaultPort].url], function (err) { exec(['open', this.state.ports[this.state.defaultPort].url], function (err) {
@ -160,11 +174,6 @@ var ContainerDetails = React.createClass({
console.log(err); console.log(err);
}); });
}, },
handleRestart: function () {
ContainerStore.restart(this.props.container.Name, function (err) {
console.log(err);
});
},
handleTerminal: function () { handleTerminal: function () {
var container = this.props.container; var container = this.props.container;
var terminal = path.join(process.cwd(), 'resources', 'terminal').replace(/ /g, '\\\\ '); var terminal = path.join(process.cwd(), 'resources', 'terminal').replace(/ /g, '\\\\ ');
@ -264,13 +273,15 @@ var ContainerDetails = React.createClass({
var state; var state;
if (this.props.container.State.Running) { if (this.props.container.State.Running) {
state = <h2 className="status running">running</h2>; state = <span className="status running">RUNNING</span>;
} else if (this.props.container.State.Restarting) { } else if (this.props.container.State.Restarting) {
state = <h2 className="status restarting">restarting</h2>; state = <span className="status restarting">RESTARTING</span>;
} else if (this.props.container.State.Paused) { } else if (this.props.container.State.Paused) {
state = <h2 className="status paused">paused</h2>; state = <span className="status paused">PAUSED</span>;
} else if (this.props.container.State.Downloading) { } else if (this.props.container.State.Downloading) {
state = <h2 className="status">downloading</h2>; state = <span className="status downloading">DOWNLOADING</span>;
} else {
state = <span className="status stopped">STOPPED</span>;
} }
var button; var button;
@ -359,6 +370,29 @@ var ContainerDetails = React.createClass({
var dropdownViewButtonClass = React.addons.classSet(assign({'dropdown-view': true}, dropdownClasses)); var dropdownViewButtonClass = React.addons.classSet(assign({'dropdown-view': true}, dropdownClasses));
var dropdownVolumeButtonClass = React.addons.classSet(assign({'dropdown-volume': 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 (
<div key={key} className="table-values">
<span className="value-left">{key}</span><span className="icon icon-arrow-right"></span>
<a className="value-right" onClick={self.handleViewLink.bind(self, val.url)}>{val.display}</a>
</div>
);
});
var volumes = _.map(self.props.container.Volumes, function (val, key) {
if (!val || val.indexOf(process.env.HOME) === -1) {
val = 'No Host Folder';
}
return (
<div key={key} className="table-values">
<span className="value-left">{key}</span><span className="icon icon-arrow-right"></span>
<a className="value-right">{val.replace(process.env.HOME, '~')}</a>
</div>
);
});
var body; var body;
if (this.props.container.State.Downloading) { if (this.props.container.State.Downloading) {
body = ( body = (
@ -375,106 +409,136 @@ var ContainerDetails = React.createClass({
</div> </div>
</div> </div>
); );
} else if (this.state.page === this.PAGE_PORTS) {
body = (
<div className="details-panel">
<div className="ports">
<h3>Configure Ports</h3>
<div className="table">
<div className="table-labels">
<div className="label-left">DOCKER PORT</div>
<div className="label-right">MAC PORT</div>
</div>
{ports}
</div>
</div>
</div>
);
} else if (this.state.page === this.PAGE_VOLUMES) {
body = (
<div className="details-panel">
<div className="volumes">
<h3>Configure Volumes</h3>
<div className="table">
<div className="table-labels">
<div className="label-left">DOCKER FOLDER</div>
<div className="label-right">MAC FOLDER</div>
</div>
{volumes}
</div>
</div>
</div>
);
} else { } else {
body = ( body = (
<div className="details-panel"> <div className="details-panel">
<div className="settings"> <div className="settings">
<h3>Container Name</h3> <div className="settings-section">
<div className="container-name"> <h3>Container Name</h3>
<input id="input-container-name" type="text" className="line" placeholder="Container Name" defaultValue={this.props.container.Name}></input> <div className="container-name">
</div> <input id="input-container-name" type="text" className="line" placeholder="Container Name" defaultValue={this.props.container.Name}></input>
<a className="btn btn-action" onClick={this.handleSaveContainerName}>Save</a>
<h3>Environment Variables</h3>
<div className="env-vars-labels">
<div className="label-key">KEY</div>
<div className="label-val">VALUE</div>
</div>
<div className="env-vars">
{envVars}
{pendingEnvVars}
<div className="keyval-row">
<input id="new-env-key" type="text" className="key line"></input>
<input id="new-env-val" type="text" className="val line"></input>
<a onClick={this.handleAddPendingEnvVar} className="only-icon btn btn-positive small"><span className="icon icon-add-1"></span></a>
</div> </div>
<a className="btn btn-action" onClick={this.handleSaveContainerName}>Save</a>
</div>
<div className="settings-section">
<h3>Environment Variables</h3>
<div className="env-vars-labels">
<div className="label-key">KEY</div>
<div className="label-val">VALUE</div>
</div>
<div className="env-vars">
{envVars}
{pendingEnvVars}
<div className="keyval-row">
<input id="new-env-key" type="text" className="key line"></input>
<input id="new-env-val" type="text" className="val line"></input>
<a onClick={this.handleAddPendingEnvVar} className="only-icon btn btn-positive small"><span className="icon icon-add-1"></span></a>
</div>
</div>
<a className="btn btn-action" onClick={this.handleSaveEnvVar}>Save</a>
</div>
<div className="settings-section">
<h3>Delete Container</h3>
<a className="btn btn-action" onClick={this.handleDeleteContainer}>Delete Container</a>
</div> </div>
<a className="btn btn-action" onClick={this.handleSaveEnvVar}>Save</a>
<h3>Delete Container</h3>
<a className="btn btn-action" onClick={this.handleDeleteContainer}>Delete Container</a>
</div> </div>
</div> </div>
); );
} }
} }
var ports = _.map(_.pairs(self.state.ports), function (pair, index, list) { var tabLogsClasses = React.addons.classSet({
var key = pair[0]; 'tab': true,
var val = pair[1]; 'active': this.state.page === this.PAGE_LOGS,
return ( disabled: this.props.container.State.Downloading
<div key={key} className="table-values">
<span className="value-left">{key}</span><span className="icon icon-arrow-right"></span>
<a className="value-right" onClick={self.handleViewLink.bind(self, val.url)}>{val.display}</a>
</div>
);
}); });
var volumes = _.map(self.props.container.Volumes, function (val, key) { var tabPortsClasses = React.addons.classSet({
if (!val || val.indexOf(process.env.HOME) === -1) { 'tab': true,
val = 'No Host Folder'; 'active': this.state.page === this.PAGE_PORTS,
} disabled: this.props.container.State.Downloading
return ( });
<div key={key} className="table-values">
<span className="value-left">{key}</span><span className="icon icon-arrow-right"></span> var tabVolumesClasses = React.addons.classSet({
<a className="value-right">{val.replace(process.env.HOME, '~')}</a> 'tab': true,
</div> '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 ( return (
<div className="details"> <div className="details">
<div className="details-header"> <div className="details-header">
<div className="details-header-info"> <h1>{this.props.container.Name}</h1><h2 className="image">{this.props.container.Config.Image}</h2>
<h1>{this.props.container.Name}</h1>{state}<h2 className="image-label">Image</h2><h2 className="image">{this.props.container.Config.Image}</h2>
</div>
<div className="details-header-actions"> <div className="details-header-actions">
<div className="action btn-group"> <span className="icon icon-preview-2 action-icon view-icon" onClick={this.handleView}></span>
<a className={viewButtonClass} onClick={this.handleView}><span className="icon icon-preview-2"></span><span className="content">View</span></a> <span className="icon icon-refresh action-icon" onClick={this.handleRestart}></span>
<a className={dropdownViewButtonClass} onClick={this.handleViewDropdown}><span className="icon-dropdown icon icon-arrow-37"></span></a> <span className="icon icon-window-code-3 action-icon" onClick={this.handleTerminal}></span>
</div> </div>
<div className="action"> </div>
<a className={dropdownVolumeButtonClass} onClick={this.handleVolumeDropdown}><span className="icon icon-folder-1"></span> <span className="content">Volumes</span> <span className="icon-dropdown icon icon-arrow-37"></span></a> <div className="details-subheader">
</div> {state}
<div className="action"> <div className="details-subheader-tabs">
<a className={buttonClass} onClick={this.handleRestart}><span className="icon icon-refresh"></span> <span className="content">Restart</span></a> <span className={tabLogsClasses} onClick={this.showLogs}>Logs</span>
</div> <span className={tabPortsClasses} onClick={this.showPorts}>Ports</span>
<div className="action"> <span className={tabVolumesClasses} onClick={this.showVolumes}>Volumes</span>
<a className={buttonClass} onClick={this.handleTerminal}><span className="icon icon-window-code-3"></span> <span className="content">Terminal</span></a> <span className={tabSettingsClasses} onClick={this.showSettings}>Settings</span>
</div>
<div className="details-header-actions-rhs tabs btn-group">
<a className={textButtonClasses} onClick={this.showLogs}><span className="icon icon-text-wrapping-2"></span></a>
<a className={gearButtonClass} onClick={this.showSettings}><span className="icon icon-setting-gear"></span></a>
</div>
</div> </div>
<Popover className={popoverViewClasses} placement="bottom">
<div className="table ports">
<div className="table-labels">
<div className="label-left">DOCKER PORT</div>
<div className="label-right">MAC PORT</div>
</div>
{ports}
</div>
</Popover>
<Popover className={popoverVolumeClasses} placement="bottom">
<div className="table volumes">
<div className="table-labels">
<div className="label-left">DOCKER FOLDER</div>
<div className="label-right">MAC FOLDER</div>
</div>
{volumes}
</div>
</Popover>
</div> </div>
{body} {body}
<Popover className={popoverViewClasses} placement="bottom">
<div className="table ports">
<div className="table-labels">
<div className="label-left">DOCKER PORT</div>
<div className="label-right">MAC PORT</div>
</div>
{ports}
</div>
</Popover>
<Popover className={popoverVolumeClasses} placement="bottom">
<div className="table volumes">
<div className="table-labels">
<div className="label-left">DOCKER FOLDER</div>
<div className="label-right">MAC FOLDER</div>
</div>
{volumes}
</div>
</Popover>
</div> </div>
); );
} }

View File

@ -7,65 +7,16 @@ var Modal = require('react-bootstrap/Modal');
var RetinaImage = require('react-retina-image'); var RetinaImage = require('react-retina-image');
var ModalTrigger = require('react-bootstrap/ModalTrigger'); var ModalTrigger = require('react-bootstrap/ModalTrigger');
var ContainerModal = require('./ContainerModal.react'); var ContainerModal = require('./ContainerModal.react');
var ContainerListItem = require('./ContainerListItem.react');
var Header = require('./Header.react'); var Header = require('./Header.react');
var docker = require('./docker'); var docker = require('./docker');
var ContainerList = React.createClass({ var ContainerList = React.createClass({
componentWillMount: function () {
this._start = Date.now();
},
render: function () { render: function () {
var self = this; var self = this;
var containers = this.props.containers.map(function (container) { 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 = <div className="state state-downloading"><div style={style} className="downloading-arrow"></div></div>;
} else if (container.State.Running && !container.State.Paused) {
state = <div className="state state-running"><div style={style} className="runningwave"></div></div>;
} else if (container.State.Restarting) {
state = <div className="state state-restarting" style={style}></div>;
} else if (container.State.Paused) {
state = <div className="state state-paused"></div>;
} else if (container.State.ExitCode) {
// state = <div className="state state-error"></div>;
state = <div className="state state-stopped"></div>;
} else {
state = <div className="state state-stopped"></div>;
}
return ( return (
<Router.Link key={container.Name} data-container={name} to="container" params={{name: container.Name}}> <ContainerListItem container={container} />
<li>
{state}
<div className="info">
<div className="name">
{container.Name}
</div>
<div className="image">
{imageName}
</div>
</div>
</li>
</Router.Link>
); );
}); });
return ( return (

View File

@ -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 = <div className="state state-downloading"><div style={style} className="downloading-arrow"></div></div>;
} else if (container.State.Running && !container.State.Paused) {
state = <div className="state state-running"><div style={style} className="runningwave"></div></div>;
} else if (container.State.Restarting) {
state = <div className="state state-restarting" style={style}></div>;
} else if (container.State.Paused) {
state = <div className="state state-paused"></div>;
} else if (container.State.ExitCode) {
// state = <div className="state state-error"></div>;
state = <div className="state state-stopped"></div>;
} else {
state = <div className="state state-stopped"></div>;
}
return (
<Router.Link key={container.Name} data-container={name} to="container" params={{name: container.Name}}>
<li onMouseEnter={self.handleItemMouseEnter} onMouseLeave={self.handleItemMouseLeave}>
{state}
<div className="info">
<div className="name">
{container.Name}
</div>
<div className="image">
{imageName}
</div>
</div>
<div className="action">
<span className="icon icon-delete-3 btn-delete"></span>
</div>
</li>
</Router.Link>
);
}
});
module.exports = ContainerListItem;

View File

@ -79,10 +79,10 @@ var Containers = React.createClass({
<div className="containers-body"> <div className="containers-body">
<div className="sidebar"> <div className="sidebar">
<section className={sidebarHeaderClass}> <section className={sidebarHeaderClass}>
<h4>My Containers</h4> <h4>Containers</h4>
<div className="create"> <div className="create">
<ModalTrigger modal={<ContainerModal/>}> <ModalTrigger modal={<ContainerModal/>}>
<a className="btn btn-action only-icon"><span className="icon icon-add-1"></span></a> <span className="btn-new icon icon-add-3"></span>
</ModalTrigger> </ModalTrigger>
</div> </div>
</section> </section>

View File

@ -40,9 +40,9 @@ var Header = React.createClass({
return ( return (
<div className="header no-drag"> <div className="header no-drag">
<div className="buttons"> <div className="buttons">
<div className="button button-close disabled"></div> <div className="button button-close red disabled"></div>
<div className="button button-minimize disabled"></div> <div className="button button-minimize yellow disabled"></div>
<div className="button button-fullscreenclose enabled" onClick={this.handleFullscreen}></div> <div className="button button-fullscreenclose green enabled" onClick={this.handleFullscreen}></div>
</div> </div>
</div> </div>
); );
@ -50,9 +50,9 @@ var Header = React.createClass({
return ( return (
<div className="header"> <div className="header">
<div className="buttons"> <div className="buttons">
<div className="button button-close enabled" onClick={this.handleClose}></div> <div className="button button-close red enabled" onClick={this.handleClose}></div>
<div className="button button-minimize enabled" onClick={this.handleMinimize}></div> <div className="button button-minimize yellow enabled" onClick={this.handleMinimize}></div>
<div className="button button-fullscreen enabled" onClick={this.handleFullscreen}></div> <div className="button button-fullscreen green enabled" onClick={this.handleFullscreen}></div>
</div> </div>
</div> </div>
); );

View File

@ -13,99 +13,6 @@
flex-direction: column; flex-direction: column;
padding: 14px 14px 20px; 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 { .question {
margin: 12px 6px 6px; margin: 12px 6px 6px;
} }
@ -122,22 +29,26 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
padding: 0px;
.sidebar { .sidebar {
padding-top: 28px;
background-color: white;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 280px; min-width: 260px;
margin: 0; margin: 0;
box-sizing: border-box; box-sizing: border-box;
border-right: 1px solid #eee; border-right: 1px solid #DCE2E2;
.sidebar-header { .sidebar-header {
flex: 0 auto; flex: 0 auto;
min-width: 240px; min-width: 240px;
min-height: 42px; min-height: 47px;
display: flex; display: flex;
border-bottom: 1px solid transparent; border-bottom: 1px solid transparent;
transition: border-bottom 0.25s; transition: border-bottom 0.25s;
padding: 0px 10px 0px 10px; //padding: 0px 10px 0px 10px;
&.sep { &.sep {
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
@ -146,27 +57,27 @@
h4 { h4 {
align-self: flex-start; align-self: flex-start;
padding: 0 24px; //padding: 0 24px;
padding-left: 26px;
margin: 14px 0 0; margin: 14px 0 0;
display: inline-block; display: inline-block;
font-size: 14px;
position: relative; position: relative;
} }
.create { .create {
flex: 1 auto; flex: 1 auto;
text-align: right; text-align: right;
/*.btn { margin-right: 20px;
margin-top: 4px; margin-top: 3px;
padding: 4px 7px;
font-size: 16px; .btn-new {
position: relative; font-size: 24px;
.icon { color: @brand-action;
position: relative; transition: all 0.25s;
top: 3px; &:hover {
left: 1px; color: darken(@brand-action, 10%);
} }
}*/ }
} }
} }
@ -176,7 +87,7 @@
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
box-sizing: border-box; box-sizing: border-box;
max-width: 280px; max-width: 260px;
&.sep { &.sep {
border-top: 1px solid #eee; border-top: 1px solid #eee;
@ -184,9 +95,7 @@
ul { ul {
margin: 0; margin: 0;
min-width: 240px;
padding: 0; padding: 0;
margin-top: 4px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -195,23 +104,29 @@
color: inherit; color: inherit;
flex-shrink: 0; flex-shrink: 0;
cursor: default; cursor: default;
margin: 0px 3px 0px 8px;
outline: none; outline: none;
padding: 4px 5px;
&.active { &.active {
li { li {
border-bottom: none; border-bottom: none;
border-radius: 40px; background-image: linear-gradient(-180deg, #24B8EB 4%, #24A3EB 100%);
background: @brand-primary;
.name { .name {
color: white; color: white;
} }
.image { .image {
color: white; 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 { .state-running {
.at2x('running-white.png', 20px, 20px); .at2x('running-white.png', 20px, 20px);
@ -244,7 +159,7 @@
li { li {
vertical-align: middle; vertical-align: middle;
padding: 10px 16px 10px 16px; padding: 10px 16px 10px 26px;
display: flex; display: flex;
flex-direction: row; 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 { .state {
margin-top: 9px; margin-top: 9px;
display: inline-block; display: inline-block;
@ -370,16 +302,108 @@
display: flex; display: flex;
flex-direction: column; 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 { .details-header {
flex: 0 auto; flex: 0 auto;
display: flex; display: flex;
flex-direction: column; flex-direction: row;
padding: 4px 40px 10px 40px; padding: 31px 24px 18px 24px;
position: relative; 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 { .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; display: flex;
flex-direction: row; flex-direction: row;
margin-top: 24px; margin-top: 24px;
@ -400,44 +424,7 @@
z-index: 0; 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 { .details-progress {
@ -449,10 +436,11 @@
flex: 1; flex: 1;
overflow: auto; overflow: auto;
.logs { .logs {
background-color: #FEFEFE;
-webkit-user-select: text; -webkit-user-select: text;
font-family: Menlo; font-family: Menlo;
font-size: 12px; font-size: 12px;
padding: 18px 35px; padding: 20px 20px;
color: lighten(@gray-normal, 6%); color: lighten(@gray-normal, 6%);
white-space: pre-wrap; white-space: pre-wrap;
p { p {
@ -460,7 +448,110 @@
} }
} }
.settings { .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; color: @gray-lightest;
margin-left: 5px; margin-left: 5px;
margin-bottom: 5px; margin-bottom: 5px;
margin-top: 20px;
.label-key { .label-key {
display: inline-block; display: inline-block;
margin-right: 30px; margin-right: 30px;

View File

@ -1,9 +1,10 @@
@import "bootstrap/bootstrap.less"; @import "bootstrap/bootstrap.less";
.header { .header {
position: absolute;
min-width: 100%; min-width: 100%;
flex: 0; flex: 0;
min-height: 50px; min-height: 30px;
-webkit-app-region: drag; -webkit-app-region: drag;
-webkit-user-select: none; -webkit-user-select: none;
// border-bottom: 1px solid #efefef; // border-bottom: 1px solid #efefef;
@ -15,8 +16,8 @@
.buttons { .buttons {
display: inline-block; display: inline-block;
position: relative; position: relative;
top: 16px; top: 10px;
left: 20px; left: 15px;
&:hover { &:hover {
.button-minimize.enabled { .button-minimize.enabled {
@ -44,7 +45,18 @@
border-radius: 6px; border-radius: 6px;
box-shadow: 0px 1px 1px 0px rgba(234,234,234,0.50); box-shadow: 0px 1px 1px 0px rgba(234,234,234,0.50);
-webkit-app-region: no-drag; -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 { &.disabled {
border: 1px solid #E8EEEF; border: 1px solid #E8EEEF;
} }

View File

@ -17,7 +17,7 @@ html, body {
overflow: hidden; overflow: hidden;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-webkit-user-select: none; -webkit-user-select: none;
font-family: 'Clear Sans', sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
cursor: default; cursor: default;
img { img {
@ -25,7 +25,7 @@ html, body {
} }
} }
::-webkit-scrollbar { /*::-webkit-scrollbar {
width: 13px; width: 13px;
} }
@ -46,7 +46,7 @@ html, body {
&:hover { &:hover {
background-color: rgba(0,0,0,0.25); background-color: rgba(0,0,0,0.25);
} }
} }*/
.question { .question {
color: @gray-lightest; color: @gray-lightest;

View File

@ -12,7 +12,7 @@ h3 {
h4 { h4 {
font-size: 13px; font-size: 13px;
color: @gray-normal; color: @gray-darker;
font-weight: 400; font-weight: 400;
} }
@ -28,7 +28,7 @@ input[type="text"] {
color: @gray-normal; color: @gray-normal;
font-weight: 300; font-weight: 300;
padding: 5px; padding: 5px;
transition: all 0.1s; transition: all 0.25s;
&:focus { &:focus {
outline: 0; outline: 0;
border-bottom: 1px solid @brand-action; border-bottom: 1px solid @brand-action;
@ -56,7 +56,7 @@ input[type="text"] {
// Mixin for generating new styles // Mixin for generating new styles
.btn-styles(@btn-color: @gray-normal) { .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 .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners
border-color: @btn-color; border-color: @btn-color;
color: @btn-color; color: @btn-color;
@ -111,7 +111,7 @@ input[type="text"] {
// Common styles // Common styles
.btn { .btn {
font-size: 12px; font-size: 13px;
background-color: transparent; background-color: transparent;
color: @gray-normal; color: @gray-normal;
border: 1px solid @gray-normal; border: 1px solid @gray-normal;
@ -119,8 +119,8 @@ input[type="text"] {
box-shadow: none; box-shadow: none;
font-weight: 400; font-weight: 400;
text-shadow: none; text-shadow: none;
padding: 6px 14px 6px 14px; padding: 4px 14px 4px 14px;
height: 32px; height: 28px;
cursor: default; cursor: default;
&.small { &.small {
@ -166,7 +166,7 @@ input[type="text"] {
&.only-icon { &.only-icon {
padding: 6px 7px 6px 7px; padding: 6px 7px 6px 7px;
&.small { &.small {
padding: 2px 5px 3px 5px; padding: 3px 5px 3px 5px;
} }
} }
} }

View File

@ -1,6 +1,6 @@
@brand-primary: #24B8EB; @brand-primary: #24B8EB;
@brand-action: #24B8EB; @brand-action: #24B8EB;
@brand-positive: #65E100; @brand-positive: #16E568;
@brand-negative: #F47A45; @brand-negative: #F47A45;
@gray-darkest: #253237; @gray-darkest: #253237;