Merge and cleanup

This commit is contained in:
Jeffrey Morgan 2015-01-25 13:29:51 -05:00
commit 2f599dc092
36 changed files with 438 additions and 199 deletions

8
.gitignore vendored
View File

@ -5,16 +5,10 @@ node_modules
npm-debug.log
# Signing Identity
script/identity
identity
# Resources
resources/virtualbox-*.pkg
resources/boot2docker*
resources/mongod
resources/MONGOD_LICENSE.txt
resources/node
resources/NODE_LICENSE.txt
resources/settings.json
# Cache
cache

View File

@ -1,3 +1,5 @@
[![bitHound Score](https://app.bithound.io/kitematic/kitematic/badges/score.svg)](http://app.bithound.io/kitematic/kitematic)
![Kitematic Logo](https://cloud.githubusercontent.com/assets/251292/5269258/1b229c3c-7a2f-11e4-96f1-e7baf3c86d73.png)
Kitematic is a simple application for managing Docker containers on Mac OS X.

View File

@ -2,8 +2,6 @@ var _ = require('underscore');
var $ = require('jquery');
var React = require('react/addons');
var Router = require('react-router');
var Convert = require('ansi-to-html');
var convert = new Convert();
var ContainerStore = require('./ContainerStore');
var docker = require('./docker');
var exec = require('exec');
@ -19,67 +17,29 @@ var RouteHandler = Router.RouteHandler;
var ContainerDetails = React.createClass({
mixins: [Router.State],
_oldHeight: 0,
PAGE_LOGS: 'logs',
PAGE_SETTINGS: 'settings',
getInitialState: function () {
return {
logs: []
logs: [],
page: this.PAGE_LOGS
};
},
logs: function () {
this.updateProgress(this.getParams().name);
/*var self = this;
var logs = [];
var index = 0;
docker.client().getContainer(this.getParams().name).logs({
follow: false,
stdout: true,
stderr: 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,
stderr: 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;
});
});
});
});*/
},
componentWillReceiveProps: function () {
this.logs();
},
componentWillMount: function () {
this.logs();
this.setState({
page: this.PAGE_LOGS
});
ContainerStore.fetchLogs(this.getParams().name, function () {
this.updateLogs();
}.bind(this));
},
componentDidMount: function () {
ContainerStore.on(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
ContainerStore.on(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
},
componentWillUnmount: function () {
ContainerStore.removeListener(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
ContainerStore.removeListener(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
},
componentDidUpdate: function () {
var parent = $('.details-logs');
@ -92,18 +52,31 @@ var ContainerDetails = React.createClass({
}
this._oldHeight = parent[0].scrollHeight - parent.height();
},
updateLogs: function (name) {
if (name && name !== this.getParams().name) {
return;
}
this.setState({
logs: ContainerStore.logs(this.getParams().name)
});
},
updateProgress: function (name) {
console.log('progress', name, ContainerStore.progress(name));
if (name === this.getParams().name) {
this.setState({
progress: ContainerStore.progress(name)
});
}
},
_escapeHTML: function (html) {
var text = document.createTextNode(html);
var div = document.createElement('div');
div.appendChild(text);
return div.innerHTML;
showLogs: function () {
this.setState({
page: this.PAGE_LOGS
});
},
showSettings: function () {
this.setState({
page: this.PAGE_SETTINGS
});
},
handleClick: function (name) {
var container = this.props.container;
@ -146,20 +119,13 @@ var ContainerDetails = React.createClass({
var state;
if (this.props.container.State.Running) {
state = <h2 className="status">running</h2>;
state = <h2 className="status running">running</h2>;
} else if (this.props.container.State.Restarting) {
state = <h2 className="status">restarting</h2>;
}
var progress;
if (this.state.progress > 0 && this.state.progress != 1) {
progress = (
<div className="details-progress">
<ProgressBar now={this.state.progress * 100} label="%(percent)s%" />
</div>
);
} else {
progress = <div></div>;
state = <h2 className="status restarting">restarting</h2>;
} else if (this.props.container.State.Paused) {
state = <h2 className="status paused">paused</h2>;
} else if (this.props.container.State.Downloading) {
state = <h2 className="status">downloading</h2>;
}
var button;
@ -169,17 +135,75 @@ var ContainerDetails = React.createClass({
button = <a className="btn btn-primary disabled" onClick={this.handleClick}>View</a>;
}
var body;
if (this.props.container.State.Downloading) {
body = (
<div className="details-progress">
<ProgressBar now={this.state.progress * 100} label="%(percent)s%" />
</div>
);
} else {
if (this.state.page === this.PAGE_LOGS) {
body = (
<div className="details-logs">
<div className="logs">
{logs}
</div>
</div>
);
} else {
body = (
<div className="details-logs">
<div className="settings">
</div>
</div>
);
}
}
var textButtonClasses = React.addons.classSet({
'btn': true,
'btn-action': true,
'only-icon': true,
'active': this.state.page === this.PAGE_LOGS
});
var gearButtonClass = React.addons.classSet({
'btn': true,
'btn-action': true,
'only-icon': true,
'active': this.state.page === this.PAGE_SETTINGS
});
var name = this.props.container.Name;
var image = this.props.container.Config.Image;
return (
<div className="details">
<div className="details-header">
<h1>{this.getParams().name}</h1> <a className="btn btn-primary" onClick={this.handleClick}>View</a>
</div>
{progress}
<div className="details-logs">
<div className="logs">
{logs}
<div className="details-header-info">
<h1>{name}</h1>{state}<h2 className="image-label">Image</h2><h2 className="image">{image}</h2>
</div>
<div className="details-header-actions">
<div className="action btn-group">
<a className="btn btn-action with-icon" onClick={this.handleClick}><span className="icon icon-preview-2"></span><span className="content">View</span></a><a className="btn btn-action with-icon dropdown-toggle"><span className="icon-dropdown icon icon-arrow-37"></span></a>
</div>
<div className="action">
<a className="btn btn-action with-icon dropdown-toggle" onClick={this.handleClick}><span className="icon icon-folder-1"></span> <span className="content">Volumes</span> <span className="icon-dropdown icon icon-arrow-37"></span></a>
</div>
<div className="action">
<a className="btn btn-action with-icon" onClick={this.handleClick}><span className="icon icon-refresh"></span> <span className="content">Restart</span></a>
</div>
<div className="action">
<a className="btn btn-action with-icon" onClick={this.handleClick}><span className="icon icon-window-code-3"></span> <span className="content">Terminal</span></a>
</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>
{body}
</div>
);
}

View File

@ -16,6 +16,14 @@ var ContainerModal = React.createClass({
},
componentDidMount: function () {
this.refs.searchInput.getDOMNode().focus();
ContainerStore.on(ContainerStore.SERVER_RECOMMENDED_EVENT, this.update);
},
update: function () {
if (!this.state.query.length) {
this.setState({
results: ContainerStore.recommended()
});
}
},
search: function (query) {
if (this._searchRequest) {
@ -91,7 +99,10 @@ var ContainerModal = React.createClass({
</div>
</div>
<div className="action">
<RetinaImage src="loading.png"/> <button className="btn btn-primary" name={r.name} onClick={self.handleClick}>Create</button>
<div className="btn-group">
<a className="btn btn-action" name={r.name} onClick={self.handleClick}>Create</a>
<a className="btn btn-action with-icon dropdown-toggle"><span className="icon-dropdown icon icon-arrow-58"></span></a>
</div>
</div>
</li>
);
@ -119,6 +130,12 @@ var ContainerModal = React.createClass({
hidden: !this.state.loading,
loading: true
});
var magnifierClasses = React.addons.classSet({
hidden: this.state.loading,
icon: true,
'icon-magnifier': true,
'search-icon': true
});
return (
<Modal {...this.props} animation={false} className="create-modal">
@ -126,6 +143,7 @@ var ContainerModal = React.createClass({
<section className="search">
<div className="search-bar">
<input type="search" ref="searchInput" className="form-control" placeholder="Find an existing image" onChange={this.handleChange}/>
<div className={magnifierClasses}></div>
<RetinaImage className={loadingClasses} src="loading.png"/>
</div>
<div className="question">
@ -137,7 +155,7 @@ var ContainerModal = React.createClass({
</div>
</section>
<aside className="custom">
<div className="title">Create a Custom Container</div>
<h4 className="title">Create a Custom Container</h4>
</aside>
</div>
</Modal>

View File

@ -1,6 +1,9 @@
var EventEmitter = require('events').EventEmitter;
var async = require('async');
var assign = require('object-assign');
var Stream = require('stream');
var Convert = require('ansi-to-html');
var convert = new Convert();
var docker = require('./docker');
var registry = require('./registry');
var $ = require('jquery');
@ -9,6 +12,7 @@ var _ = require('underscore');
var _recommended = [];
var _containers = {};
var _progress = {};
var _logs = {};
var ContainerStore = assign(EventEmitter.prototype, {
CLIENT_CONTAINER_EVENT: 'client_container',
@ -19,10 +23,6 @@ var ContainerStore = assign(EventEmitter.prototype, {
_pullScratchImage: function (callback) {
var image = docker.client().getImage('scratch:latest');
image.inspect(function (err, data) {
if (err) {
callback(err);
return;
}
if (!data) {
docker.client().pull('scratch:latest', function (err, stream) {
if (err) {
@ -69,6 +69,7 @@ var ContainerStore = assign(EventEmitter.prototype, {
stream.on('data', function (str) {
var data = JSON.parse(str);
console.log(data);
if (data.status === 'Already exists') {
layerProgress[data.id] = 1;
@ -97,6 +98,12 @@ var ContainerStore = assign(EventEmitter.prototype, {
});
});
},
_escapeHTML: function (html) {
var text = document.createTextNode(html);
var div = document.createElement('div');
div.appendChild(text);
return div.innerHTML;
},
_createContainer: function (image, name, callback) {
var existing = docker.client().getContainer(name);
var self = this;
@ -124,7 +131,6 @@ var ContainerStore = assign(EventEmitter.prototype, {
});
},
_createPlaceholderContainer: function (imageName, name, callback) {
console.log('_createPlaceholderContainer', imageName, name);
var self = this;
this._pullScratchImage(function (err) {
if (err) {
@ -194,7 +200,6 @@ var ContainerStore = assign(EventEmitter.prototype, {
// If the event is delete, remove the container
if (data.status === 'destroy') {
console.log('destroy');
var container = _.findWhere(_.values(_containers), {Id: data.id});
delete _containers[container.Name];
this.emit(this.SERVER_CONTAINER_EVENT, container.Name, data.status);
@ -278,6 +283,54 @@ var ContainerStore = assign(EventEmitter.prototype, {
}
});
},
fetchLogs: function (name, callback) {
if (_logs[name]) {
callback();
}
_logs[name] = [];
var index = 0;
var self = this;
docker.client().getContainer(name).logs({
follow: false,
stdout: true,
stderr: 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[name].push(convert.toHtml(self._escapeHTML(msg)));
self.emit(self.SERVER_LOGS_EVENT, name);
}
index += 1;
});
stream.on('end', function (buf) {
callback();
docker.client().getContainer(name).logs({
follow: true,
stdout: true,
stderr: 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[name].push(convert.toHtml(self._escapeHTML(msg)));
self.emit(self.SERVER_LOGS_EVENT, name);
}
index += 1;
});
});
});
});
},
create: function (repository, tag, callback) {
tag = tag || 'latest';
var self = this;
@ -338,7 +391,7 @@ var ContainerStore = assign(EventEmitter.prototype, {
return _progress[name];
},
logs: function (name) {
return logs[name];
return _logs[name] || [];
}
});

View File

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

View File

@ -5,7 +5,6 @@ var NoContainers = React.createClass({
render: function () {
return (
<div className="no-containers">
<RetinaImage src="roundedcontainer.png"/>
<h3>No Containers</h3>
</div>
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 727 B

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 536 B

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 654 B

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

View File

@ -10,7 +10,7 @@
.sidebar {
display: flex;
flex-direction: column;
min-width: 240px;
min-width: 280px;
margin: 0;
box-sizing: border-box;
border-right: 1px solid #eee;
@ -22,49 +22,36 @@
display: flex;
border-bottom: 1px solid transparent;
transition: border-bottom 0.25s;
padding: 0px 10px 0px 10px;
&.sep {
border-bottom: 1px solid #eee;
box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.03);
}
h3 {
h4 {
align-self: flex-start;
color: #CCD3D5;
font-size: 18px;
font-weight: 400;
padding: 0 24px;
margin: 10px 0 0;
font-variant: small-caps;
margin: 14px 0 0;
display: inline-block;
font-size: 14px;
position: relative;
}
.create {
flex: 1 auto;
text-align: right;
.wrapper {
text-align: center;
display: inline-block;
width: 50px;
span.icon {
margin-top: 5px;
margin-left: auto;
display: inline-block;
border-radius: 20px;
font-size: 26px;
color: @brand-primary;
}
&:hover {
span.icon {
color: darken(@brand-primary, 20%);
}
.btn {
margin-top: 4px;
padding: 4px 7px;
font-size: 16px;
position: relative;
.icon {
position: relative;
top: 3px;
left: 1px;
}
}
}
}
@ -83,6 +70,7 @@
margin: 0;
min-width: 240px;
padding: 0;
margin-top: 4px;
display: flex;
flex-direction: column;
@ -91,12 +79,41 @@
color: inherit;
flex-shrink: 0;
cursor: default;
margin: 0px 3px 0px 8px;
outline: none;
padding: 4px 5px;
&.active {
background: #eee;
li {
border-bottom: none;
border-radius: 40px;
background: @brand-primary;
.name {
color: white;
}
.image {
color: white;
opacity: 0.9;
}
&:hover {
.state-running {
.at2x('running-white.png', 20px, 20px);
.runningwave {
.at2x('runningwave-white.png', 20px, 20px);
}
}
.state-stopped {
.at2x('stopped-white.png', 20px, 20px);
}
.state-downloading {
.at2x('downloading-white.png', 20px, 20px);
.downloading-arrow {
.at2x('downloading-arrow-white.png', 20px, 20px);
}
}
}
}
@ -111,8 +128,7 @@
li {
vertical-align: middle;
padding-bottom: 14px;
margin: 16px 24px 0px;
padding: 10px 16px 10px 16px;
display: flex;
flex-direction: row;
@ -127,10 +143,10 @@
overflow: hidden;
font-size: 14px;
font-weight: 400;
color: #555;
color: @gray-darkest;
}
.image {
color: #999;
color: @gray-lighter;
font-size: 12px;
font-weight: 400;
text-overflow: ellipsis;
@ -210,12 +226,6 @@
}
}
}
.status {
font-size: 12px;
font-variant: small-caps;
color: @brand-primary;
}
}
.no-containers {
@ -224,9 +234,12 @@
align-items: center;
justify-content: center;
flex-direction: column;
position: relative;
h3 {
font-size: 16px;
position: relative;
top: -44px;
font-size: 18px;
color: #C7D7D7;
}
}
@ -244,29 +257,69 @@
.details-header {
flex: 0 auto;
display: flex;
flex-direction: row;
padding: 0px 45px 14px;
flex-direction: column;
padding: 4px 40px 10px 40px;
position: relative;
a {
position: absolute;
right: 30px;
top: -4px;
}
h1 {
font-size: 24px;
margin: 0;
}
h2 {
margin-left: 18px;
font-size: 14px;
font-variant: small-caps;
border-bottom: 1px solid #eee;
&.status {
color: @brand-success;
.details-header-actions {
flex: 0 auto;
display: flex;
flex-direction: row;
margin-top: 24px;
margin-bottom: 6px;
position: relative;
border-bottom: 1px solid transparent;
transition: border-bottom 0.25s;
.action {
flex: 0 auto;
margin-right: 24px;
}
.details-header-actions-rhs {
flex: 1 auto;
display: flex;
align-items: right;
justify-content: flex-end;
a.btn {
z-index: 0;
}
}
}
&.image {
.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;
}
}
}
}
@ -279,12 +332,17 @@
.details-logs {
flex: 1;
overflow: auto;
h4 {
font-size: 14px;
margin-top: 16px;
margin-left: 40px;
}
.logs {
-webkit-user-select: text;
font-family: Menlo;
font-size: 12px;
padding: 44px 45px;
color: #595D5E;
padding: 18px 45px;
color: lighten(@gray-normal, 6%);
white-space: pre-wrap;
p {
margin: 0 6px;

View File

@ -3,9 +3,10 @@
.header {
min-width: 100%;
flex: 0;
min-height: 48px;
min-height: 50px;
-webkit-app-region: drag;
-webkit-user-select: none;
// border-bottom: 1px solid #efefef;
&.no-drag {
-webkit-app-region: no-drag;

View File

@ -15,6 +15,11 @@ html, body {
-webkit-font-smoothing: antialiased;
-webkit-user-select: none;
font-family: 'Clear Sans', sans-serif;
cursor: default;
img {
pointer-events: none;
}
}
::-webkit-scrollbar {
@ -57,12 +62,6 @@ html, body {
flex-direction: row;
padding: 32px 32px;
.title {
color: #CCD3D5;
font-weight: 400;
font-size: 13px;
}
aside.custom {
flex: 0 auto;
padding-left: 32px;
@ -76,7 +75,10 @@ html, body {
.question {
a {
color: #CCD3D5;
color: @gray-lightest;
&:hover {
color: darken(@gray-lightest, 10%);
}
}
font-size: 10px;
text-align: right;
@ -86,27 +88,35 @@ html, body {
position: relative;
.loading {
position: absolute;
right: 9px;
top: 7px;
width: 24px;
height: 24px;
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;
font-weight: 400;
color: #666;
padding: 8px 16px 8px 40px;
color: @gray-darkest;
margin-bottom: 3px;
border-color: @gray-lightest;
box-shadow: none;
&:focus {
box-shadow: none;
border-color: #bbb;
border-color: @gray-lighter;
}
&::-webkit-input-placeholder {
@ -134,17 +144,25 @@ html, body {
}
ul {
margin-top: 10px;
list-style: none;
color: #555;
padding: 0;
li {
&:hover {
background-color: lighten(@gray-lightest, 17.5%);
}
display: flex;
flex-direction: row;
margin: 12px;
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;
@ -156,7 +174,7 @@ html, body {
text-overflow: ellipsis;
}
.properties {
color: #A7A7A7;
color: @gray-lighter;
margin-top: 2px;
.star-count {
@ -178,6 +196,8 @@ html, body {
flex: 0 auto;
}
.action {
position: relative;
top: 5px;
text-align: right;
flex: 1 auto;
}

View File

@ -6,6 +6,12 @@
@import "bootstrap/mixins.less";
h4 {
font-size: 13px;
color: @gray-normal;
font-weight: 400;
}
//
// Buttons
// --------------------------------------------------
@ -21,32 +27,83 @@
}
// Mixin for generating new styles
.btn-styles(@btn-color: #555) {
.btn-styles(@btn-color: @gray-normal) {
transition: all 0.1s;
.reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners
background-repeat: repeat-x;
// border-color: darken(@btn-color, 14%);
border-color: @btn-color;
color: @btn-color;
&:hover,
&:focus {
background-color: darken(@btn-color, 12%);
&:focus {
border-color: darken(@btn-color, 10%);
color: darken(@btn-color, 10%);
cursor: default;
box-shadow: none;
}
&:active {
background-color: lighten(@btn-color, 45%);
border-color: darken(@btn-color, 10%);
color: darken(@btn-color, 10%);
box-shadow: none;
}
&.active {
background-color: @btn-color;
color: white;
box-shadow: none;
box-shadow: none;
}
&:disabled,
&[disabled] {
background-color: darken(@btn-color, 12%);
opacity: 0.5;
}
}
.btn-group {
.btn {
.icon-dropdown {
&.icon:before {
top: 7px;
margin-left: 0px;
margin-right: 4px;
}
}
}
}
// Common styles
.btn {
font-size: 12px;
background-color: transparent;
color: @gray-normal;
border: 1px solid @gray-normal;
border-radius: 25px;
box-shadow: none;
font-weight: 400;
text-shadow: none;
padding: 6px 14px 6px 14px;
height: 32px;
cursor: default;
.content {
position: relative;
top: -4px;
margin-left: 5px;
margin-right: 5px;
}
.icon {
position: relative;
font-size: 16px;
}
// Remove the gradient for the pressed/active state
&:active,
&.active {
background-image: none;
box-shadow: none;
}
&:focus,
@ -57,6 +114,9 @@
}
// Apply the mixin to the buttons
.btn-action {
.btn-styles(@brand-action);
}
.btn-default { .btn-styles(@btn-default-bg); }
.btn-primary { .btn-styles(@btn-primary-bg); }
.btn-success { .btn-styles(@btn-success-bg); }

View File

@ -1,4 +1,10 @@
@brand-primary: #24B8EB;
@brand-action: #49CEF2;
@brand-positive: #3AD86D;
@brand-negative: #F74B1F;
@brand-action: #24B8EB;
@brand-positive: #65E100;
@brand-negative: #F47A45;
@gray-darkest: #253237;
@gray-darker: #394C51;
@gray-normal: #546C70;
@gray-lighter: #7A9999;
@gray-lightest: #C7D7D7;

View File

@ -27,9 +27,9 @@ app.on('activate-with-no-open-windows', function () {
app.on('ready', function() {
var windowOptions = {
width: 1200,
height: 800,
'min-width': 960,
width: 1000,
height: 700,
'min-width': 1000,
'min-height': 700,
resizable: true,
frame: false

View File

@ -1,7 +1,6 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
BASE=$DIR/..
BASE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
export BOOT2DOCKER_CLI_VERSION=$(node -pe "JSON.parse(process.argv[1])['boot2docker-version']" "$(cat $BASE/package.json)")
export BOOT2DOCKER_CLI_VERSION_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION

View File

@ -22,21 +22,28 @@ var downloadatomshell = require('gulp-download-atom-shell');
var packagejson = require('./package.json');
var http = require('http');
var react = require('gulp-react');
var fs = require('fs');
var dependencies = Object.keys(packagejson.dependencies);
var devDependencies = Object.keys(packagejson.devDependencies);
var options = {
dev: process.argv.indexOf('release') === -1,
dev: process.argv.indexOf('release') === -1 && process.argv.indexOf('test') === -1,
test: process.argv.indexOf('test') !== -1,
filename: 'Kitematic.app',
name: 'Kitematic',
signing_identity: process.env.XCODE_SIGNING_IDENTITY
signing_identity: fs.readFileSync('./identity')
};
gulp.task('js', function () {
gulp.src('./app/**/*.js')
.pipe(plumber(function(error) {
gutil.log(gutil.colors.red('Error (' + error.plugin + '): ' + error.message));
// emit the end event, to properly end the task
this.emit('end');
}))
.pipe(react())
.pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build'));
.pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build'))
.pipe(gulpif(options.dev, livereload()));
});
gulp.task('specs', function () {
@ -74,7 +81,7 @@ gulp.task('images', function() {
svgoPlugins: [{removeViewBox: false}]
}))
.pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build'))
.pipe(gulpif(options.dev && !options.test, livereload()));
.pipe(gulpif(options.dev, livereload()));
});
gulp.task('styles', function () {
@ -103,11 +110,11 @@ gulp.task('download', function (cb) {
gulp.task('copy', function () {
gulp.src('./app/index.html')
.pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build'))
.pipe(gulpif(options.dev && !options.test, livereload()));
.pipe(gulpif(options.dev, livereload()));
gulp.src('./app/fonts/**')
.pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build'))
.pipe(gulpif(options.dev && !options.test, livereload()));
.pipe(gulpif(options.dev, livereload()));
});
gulp.task('dist', function (cb) {

View File

@ -12,7 +12,7 @@
"bugs": "https://github.com/kitematic/kitematic/issues",
"scripts": {
"start": "gulp",
"preinstall": "./script/deps",
"preinstall": "./deps",
"test": "gulp test",
"release": ". ./script/identity && gulp release"
},