Merge and cleanup
|
@ -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
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
[](http://app.bithound.io/kitematic/kitematic)
|
||||
|
||||

|
||||
|
||||
Kitematic is a simple application for managing Docker containers on Mac OS X.
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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] || [];
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -5,7 +5,6 @@ var NoContainers = React.createClass({
|
|||
render: function () {
|
||||
return (
|
||||
<div className="no-containers">
|
||||
<RetinaImage src="roundedcontainer.png"/>
|
||||
<h3>No Containers</h3>
|
||||
</div>
|
||||
);
|
||||
|
|
After Width: | Height: | Size: 208 B |
After Width: | Height: | Size: 412 B |
After Width: | Height: | Size: 410 B |
After Width: | Height: | Size: 852 B |
Before Width: | Height: | Size: 619 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 727 B After Width: | Height: | Size: 572 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 359 B |
After Width: | Height: | Size: 731 B |
Before Width: | Height: | Size: 536 B After Width: | Height: | Size: 527 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 252 B |
After Width: | Height: | Size: 461 B |
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 340 B |
Before Width: | Height: | Size: 654 B After Width: | Height: | Size: 626 B |
After Width: | Height: | Size: 349 B |
After Width: | Height: | Size: 729 B |
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
19
gulpfile.js
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|