Merge pull request #163 from kitematic/jmorgan-container-merge
Update Existing containers
|
@ -2,9 +2,13 @@ var _ = require('underscore');
|
||||||
var $ = require('jquery');
|
var $ = require('jquery');
|
||||||
var React = require('react/addons');
|
var React = require('react/addons');
|
||||||
var Router = require('react-router');
|
var Router = require('react-router');
|
||||||
var ContainerStore = require('./ContainerStore');
|
|
||||||
var docker = require('./docker');
|
|
||||||
var exec = require('exec');
|
var exec = require('exec');
|
||||||
|
var path = require('path');
|
||||||
|
var remote = require('remote');
|
||||||
|
var dialog = remote.require('dialog');
|
||||||
|
var ContainerStore = require('./ContainerStore');
|
||||||
|
var ContainerUtil = require('./ContainerUtil');
|
||||||
|
var docker = require('./docker');
|
||||||
var boot2docker = require('./boot2docker');
|
var boot2docker = require('./boot2docker');
|
||||||
var ProgressBar = require('react-bootstrap/ProgressBar');
|
var ProgressBar = require('react-bootstrap/ProgressBar');
|
||||||
|
|
||||||
|
@ -22,22 +26,23 @@ var ContainerDetails = React.createClass({
|
||||||
getInitialState: function () {
|
getInitialState: function () {
|
||||||
return {
|
return {
|
||||||
logs: [],
|
logs: [],
|
||||||
page: this.PAGE_LOGS
|
page: this.PAGE_LOGS,
|
||||||
|
env: {},
|
||||||
|
pendingEnv: {}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentWillReceiveProps: function () {
|
componentWillReceiveProps: function () {
|
||||||
this.setState({
|
this.init();
|
||||||
page: this.PAGE_LOGS
|
},
|
||||||
});
|
componentWillMount: function () {
|
||||||
ContainerStore.fetchLogs(this.getParams().name, function () {
|
this.init();
|
||||||
this.updateLogs();
|
|
||||||
}.bind(this));
|
|
||||||
},
|
},
|
||||||
componentDidMount: function () {
|
componentDidMount: function () {
|
||||||
ContainerStore.on(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
|
ContainerStore.on(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
|
||||||
ContainerStore.on(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
ContainerStore.on(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||||
},
|
},
|
||||||
componentWillUnmount: function () {
|
componentWillUnmount: function () {
|
||||||
|
// app close
|
||||||
ContainerStore.removeListener(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
|
ContainerStore.removeListener(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
|
||||||
ContainerStore.removeListener(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
ContainerStore.removeListener(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||||
},
|
},
|
||||||
|
@ -52,6 +57,14 @@ var ContainerDetails = React.createClass({
|
||||||
}
|
}
|
||||||
this._oldHeight = parent[0].scrollHeight - parent.height();
|
this._oldHeight = parent[0].scrollHeight - parent.height();
|
||||||
},
|
},
|
||||||
|
init: function () {
|
||||||
|
this.setState({
|
||||||
|
env: ContainerUtil.env(ContainerStore.container(this.getParams().name))
|
||||||
|
});
|
||||||
|
ContainerStore.fetchLogs(this.getParams().name, function () {
|
||||||
|
this.updateLogs();
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
updateLogs: function (name) {
|
updateLogs: function (name) {
|
||||||
if (name && name !== this.getParams().name) {
|
if (name && name !== this.getParams().name) {
|
||||||
return;
|
return;
|
||||||
|
@ -78,7 +91,7 @@ var ContainerDetails = React.createClass({
|
||||||
page: this.PAGE_SETTINGS
|
page: this.PAGE_SETTINGS
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleClick: function (name) {
|
handleView: function () {
|
||||||
var container = this.props.container;
|
var container = this.props.container;
|
||||||
boot2docker.ip(function (err, ip) {
|
boot2docker.ip(function (err, ip) {
|
||||||
var ports = _.map(container.NetworkSettings.Ports, function (value, key) {
|
var ports = _.map(container.NetworkSettings.Ports, function (value, key) {
|
||||||
|
@ -102,6 +115,66 @@ var ContainerDetails = React.createClass({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
handleTerminal: function () {
|
||||||
|
var container = this.props.container;
|
||||||
|
var terminal = path.join(process.cwd(), 'resources', 'terminal').replace(/ /g, '\\\\ ');
|
||||||
|
var cmd = [terminal, boot2docker.command().replace(/ /g, '\\\\ '), 'ssh', '-t', 'sudo', 'docker', 'exec', '-i', '-t', container.Name, 'bash'];
|
||||||
|
exec(cmd, function (stderr, stdout, code) {
|
||||||
|
if (code) {
|
||||||
|
console.log(stderr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleSaveEnvVar: function () {
|
||||||
|
var $rows = $('.env-vars .keyval-row');
|
||||||
|
var envVarList = [];
|
||||||
|
$rows.each(function () {
|
||||||
|
var key = $(this).find('.key').val();
|
||||||
|
var val = $(this).find('.val').val();
|
||||||
|
if (!key.length || !val.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
envVarList.push(key + '=' + val);
|
||||||
|
});
|
||||||
|
ContainerStore.updateContainer(this.props.container.Name, {
|
||||||
|
Env: envVarList
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleAddPendingEnvVar: function () {
|
||||||
|
var newKey = $('#new-env-key').val();
|
||||||
|
var newVal = $('#new-env-val').val();
|
||||||
|
var newEnv = {};
|
||||||
|
newEnv[newKey] = newVal;
|
||||||
|
this.setState({
|
||||||
|
pendingEnv: _.extend(this.state.pendingEnv, newEnv)
|
||||||
|
});
|
||||||
|
$('#new-env-key').val('');
|
||||||
|
$('#new-env-val').val('');
|
||||||
|
},
|
||||||
|
handleRemoveEnvVar: function (key) {
|
||||||
|
var newEnv = _.omit(this.state.env, key);
|
||||||
|
this.setState({
|
||||||
|
env: newEnv
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleRemovePendingEnvVar: function (key) {
|
||||||
|
var newEnv = _.omit(this.state.pendingEnv, key);
|
||||||
|
this.setState({
|
||||||
|
pendingEnv: newEnv
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleDeleteContainer: function () {
|
||||||
|
dialog.showMessageBox({
|
||||||
|
message: 'Are you sure you want to delete this container?',
|
||||||
|
buttons: ['Delete', 'Cancel']
|
||||||
|
}, function (index) {
|
||||||
|
if (index === 0) {
|
||||||
|
ContainerStore.remove(this.props.container.Name, function (err) {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
@ -135,6 +208,25 @@ var ContainerDetails = React.createClass({
|
||||||
button = <a className="btn btn-primary disabled" onClick={this.handleClick}>View</a>;
|
button = <a className="btn btn-primary disabled" onClick={this.handleClick}>View</a>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var envVars = _.map(this.state.env, function (val, key) {
|
||||||
|
return (
|
||||||
|
<div key={key} className="keyval-row">
|
||||||
|
<input type="text" className="key line" defaultValue={key}></input>
|
||||||
|
<input type="text" className="val line" defaultValue={val}></input>
|
||||||
|
<a onClick={self.handleRemoveEnvVar.bind(self, key)} className="only-icon btn btn-action small"><span className="icon icon-cross"></span></a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
var pendingEnvVars = _.map(this.state.pendingEnv, function (val, key) {
|
||||||
|
return (
|
||||||
|
<div key={key} className="keyval-row">
|
||||||
|
<input type="text" className="key line" defaultValue={key}></input>
|
||||||
|
<input type="text" className="val line" defaultValue={val}></input>
|
||||||
|
<a onClick={self.handleRemovePendingEnvVar.bind(self, key)} className="only-icon btn btn-action small"><span className="icon icon-arrow-undo"></span></a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
var body;
|
var body;
|
||||||
if (this.props.container.State.Downloading) {
|
if (this.props.container.State.Downloading) {
|
||||||
body = (
|
body = (
|
||||||
|
@ -145,7 +237,7 @@ var ContainerDetails = React.createClass({
|
||||||
} else {
|
} else {
|
||||||
if (this.state.page === this.PAGE_LOGS) {
|
if (this.state.page === this.PAGE_LOGS) {
|
||||||
body = (
|
body = (
|
||||||
<div className="details-logs">
|
<div className="details-panel details-logs">
|
||||||
<div className="logs">
|
<div className="logs">
|
||||||
{logs}
|
{logs}
|
||||||
</div>
|
</div>
|
||||||
|
@ -153,16 +245,33 @@ var ContainerDetails = React.createClass({
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
body = (
|
body = (
|
||||||
<div className="details-logs">
|
<div className="details-panel">
|
||||||
<div className="settings">
|
<div className="settings">
|
||||||
|
<h3>Container Name</h3>
|
||||||
|
<input id="input-container-name" type="text" className="line" placeholder="Container Name" defaultValue={this.props.container.Name}></input>
|
||||||
|
<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>
|
||||||
|
<h3>Delete Container</h3>
|
||||||
|
<a className="btn btn-action" onClick={this.handleDeleteContainer}>Delete Container</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var name = this.props.container.Name;
|
|
||||||
var image = this.props.container.Config.Image;
|
|
||||||
var disabledClass = '';
|
var disabledClass = '';
|
||||||
if (!this.props.container.State.Running) {
|
if (!this.props.container.State.Running) {
|
||||||
disabledClass = 'disabled';
|
disabledClass = 'disabled';
|
||||||
|
@ -186,7 +295,7 @@ var ContainerDetails = React.createClass({
|
||||||
'btn-action': true,
|
'btn-action': true,
|
||||||
'only-icon': true,
|
'only-icon': true,
|
||||||
'active': this.state.page === this.PAGE_LOGS,
|
'active': this.state.page === this.PAGE_LOGS,
|
||||||
disabled: !this.props.container.State.Running
|
disabled: this.props.container.State.Downloading
|
||||||
});
|
});
|
||||||
|
|
||||||
var gearButtonClass = React.addons.classSet({
|
var gearButtonClass = React.addons.classSet({
|
||||||
|
@ -194,27 +303,27 @@ var ContainerDetails = React.createClass({
|
||||||
'btn-action': true,
|
'btn-action': true,
|
||||||
'only-icon': true,
|
'only-icon': true,
|
||||||
'active': this.state.page === this.PAGE_SETTINGS,
|
'active': this.state.page === this.PAGE_SETTINGS,
|
||||||
disabled: !this.props.container.State.Running
|
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">
|
<div className="details-header-info">
|
||||||
<h1>{name}</h1>{state}<h2 className="image-label">Image</h2><h2 className="image">{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>
|
||||||
<div className="details-header-actions">
|
<div className="details-header-actions">
|
||||||
<div className="action btn-group">
|
<div className="action btn-group">
|
||||||
<a className={buttonClass} onClick={this.handleClick}><span className="icon icon-preview-2"></span><span className="content">View</span></a><a className={dropdownButtonClass}><span className="icon-dropdown icon icon-arrow-37"></span></a>
|
<a className={buttonClass} onClick={this.handleView}><span className="icon icon-preview-2"></span><span className="content">View</span></a><a className={dropdownButtonClass}><span className="icon-dropdown icon icon-arrow-37"></span></a>
|
||||||
</div>
|
</div>
|
||||||
<div className="action">
|
<div className="action">
|
||||||
<a className={dropdownButtonClass} 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>
|
<a className={dropdownButtonClass} onClick={this.handleView}><span className="icon icon-folder-1"></span> <span className="content">Volumes</span> <span className="icon-dropdown icon icon-arrow-37"></span></a>
|
||||||
</div>
|
</div>
|
||||||
<div className="action">
|
<div className="action">
|
||||||
<a className={buttonClass} onClick={this.handleClick}><span className="icon icon-refresh"></span> <span className="content">Restart</span></a>
|
<a className={buttonClass} onClick={this.handleView}><span className="icon icon-refresh"></span> <span className="content">Restart</span></a>
|
||||||
</div>
|
</div>
|
||||||
<div className="action">
|
<div className="action">
|
||||||
<a className={buttonClass} onClick={this.handleClick}><span className="icon icon-window-code-3"></span> <span className="content">Terminal</span></a>
|
<a className={buttonClass} onClick={this.handleTerminal}><span className="icon icon-window-code-3"></span> <span className="content">Terminal</span></a>
|
||||||
</div>
|
</div>
|
||||||
<div className="details-header-actions-rhs tabs btn-group">
|
<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={textButtonClasses} onClick={this.showLogs}><span className="icon icon-text-wrapping-2"></span></a>
|
||||||
|
|
|
@ -10,6 +10,8 @@ var MenuItem = require('react-bootstrap/MenuItem');
|
||||||
|
|
||||||
var RetinaImage = require('react-retina-image');
|
var RetinaImage = require('react-retina-image');
|
||||||
var ContainerStore = require('./ContainerStore');
|
var ContainerStore = require('./ContainerStore');
|
||||||
|
var OverlayTrigger = require('react-bootstrap/OverlayTrigger');
|
||||||
|
var Popover = require('react-bootstrap/Popover');
|
||||||
|
|
||||||
var ContainerModal = React.createClass({
|
var ContainerModal = React.createClass({
|
||||||
_searchRequest: null,
|
_searchRequest: null,
|
||||||
|
@ -152,8 +154,7 @@ var ContainerModal = React.createClass({
|
||||||
<div className="btn-group">
|
<div className="btn-group">
|
||||||
<button type="button" className="btn btn-primary" onClick={self.handleClick.bind(self, r.name)}>Create</button>
|
<button type="button" className="btn btn-primary" onClick={self.handleClick.bind(self, r.name)}>Create</button>
|
||||||
<button type="button" className="btn btn-primary dropdown-toggle" onClick={self.handleDropdownClick.bind(self, r.name)} data-name={r.name}>
|
<button type="button" className="btn btn-primary dropdown-toggle" onClick={self.handleDropdownClick.bind(self, r.name)} data-name={r.name}>
|
||||||
<span className="caret"></span>
|
<span className="icon-dropdown icon icon-arrow-37"></span>
|
||||||
<span className="sr-only">Toggle Dropdown</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,16 +3,19 @@ var async = require('async');
|
||||||
var assign = require('object-assign');
|
var assign = require('object-assign');
|
||||||
var Stream = require('stream');
|
var Stream = require('stream');
|
||||||
var Convert = require('ansi-to-html');
|
var Convert = require('ansi-to-html');
|
||||||
var convert = new Convert();
|
|
||||||
var docker = require('./docker');
|
var docker = require('./docker');
|
||||||
var registry = require('./registry');
|
var registry = require('./registry');
|
||||||
|
var ContainerUtil = require('./ContainerUtil');
|
||||||
var $ = require('jquery');
|
var $ = require('jquery');
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
var convert = new Convert();
|
||||||
|
|
||||||
var _recommended = [];
|
var _recommended = [];
|
||||||
var _containers = {};
|
var _containers = {};
|
||||||
var _progress = {};
|
var _progress = {};
|
||||||
var _logs = {};
|
var _logs = {};
|
||||||
|
var _muted = {};
|
||||||
|
|
||||||
var ContainerStore = assign(EventEmitter.prototype, {
|
var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
CLIENT_CONTAINER_EVENT: 'client_container',
|
CLIENT_CONTAINER_EVENT: 'client_container',
|
||||||
|
@ -104,20 +107,23 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
div.appendChild(text);
|
div.appendChild(text);
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
},
|
},
|
||||||
_createContainer: function (image, name, callback) {
|
_createContainer: function (name, containerData, callback) {
|
||||||
var existing = docker.client().getContainer(name);
|
var existing = docker.client().getContainer(name);
|
||||||
var self = this;
|
var self = this;
|
||||||
|
containerData.name = name;
|
||||||
|
if (containerData.Config && containerData.Config.Image) {
|
||||||
|
containerData.Image = containerData.Config.Image;
|
||||||
|
}
|
||||||
|
existing.kill(function (err, data) {
|
||||||
existing.remove(function (err, data) {
|
existing.remove(function (err, data) {
|
||||||
docker.client().createContainer({
|
docker.client().createContainer(containerData, function (err, container) {
|
||||||
Image: image,
|
|
||||||
Tty: false,
|
|
||||||
name: name,
|
|
||||||
User: 'root'
|
|
||||||
}, function (err, container) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err, null);
|
callback(err, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (containerData.State && !containerData.State.Running) {
|
||||||
|
self.fetchContainer(name, callback);
|
||||||
|
} else {
|
||||||
container.start({
|
container.start({
|
||||||
PublishAllPorts: true
|
PublishAllPorts: true
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
|
@ -127,6 +133,8 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
}
|
}
|
||||||
self.fetchContainer(name, callback);
|
self.fetchContainer(name, callback);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -181,7 +189,7 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
stream.setEncoding('utf8');
|
stream.setEncoding('utf8');
|
||||||
stream.on('data', function (data) {});
|
stream.on('data', function (data) {});
|
||||||
stream.on('end', function () {
|
stream.on('end', function () {
|
||||||
self._createContainer(container.KitematicDownloadingImage, container.Name, function () {});
|
self._createContainer(container.Name, {Image: container.KitematicDownloadingImage}, function () {});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -201,11 +209,20 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
// If the event is delete, remove the container
|
// If the event is delete, remove the container
|
||||||
if (data.status === 'destroy') {
|
if (data.status === 'destroy') {
|
||||||
var container = _.findWhere(_.values(_containers), {Id: data.id});
|
var container = _.findWhere(_.values(_containers), {Id: data.id});
|
||||||
|
if (!container || _muted[container.Name]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
delete _containers[container.Name];
|
delete _containers[container.Name];
|
||||||
this.emit(this.SERVER_CONTAINER_EVENT, container.Name, data.status);
|
this.emit(this.SERVER_CONTAINER_EVENT, container.Name, data.status);
|
||||||
} else {
|
} else {
|
||||||
this.fetchContainer(data.id, function (err) {
|
this.fetchContainer(data.id, function (err) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var container = _.findWhere(_.values(_containers), {Id: data.id});
|
var container = _.findWhere(_.values(_containers), {Id: data.id});
|
||||||
|
if (!container || _muted[container.Name]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.emit(this.SERVER_CONTAINER_EVENT, container ? container.Name : null, data.status);
|
this.emit(this.SERVER_CONTAINER_EVENT, container ? container.Name : null, data.status);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}
|
}
|
||||||
|
@ -227,11 +244,15 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
|
if (container.Config.Image === container.Image.slice(0, 12) || container.Config.Image === container.Image) {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Fix leading slash in container names
|
// Fix leading slash in container names
|
||||||
container.Name = container.Name.replace('/', '');
|
container.Name = container.Name.replace('/', '');
|
||||||
|
|
||||||
// Add Downloading State (stored in environment variables) to containers for Kitematic
|
// Add Downloading State (stored in environment variables) to containers for Kitematic
|
||||||
var env = _.object(container.Config.Env.map(function (e) { return e.split('='); }));
|
var env = ContainerUtil.env(container);
|
||||||
container.State.Downloading = !!env.KITEMATIC_DOWNLOADING;
|
container.State.Downloading = !!env.KITEMATIC_DOWNLOADING;
|
||||||
container.KitematicDownloadingImage = env.KITEMATIC_DOWNLOADING_IMAGE;
|
container.KitematicDownloadingImage = env.KITEMATIC_DOWNLOADING_IMAGE;
|
||||||
|
|
||||||
|
@ -269,7 +290,6 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
async.map(recommended, function (repository, callback) {
|
async.map(recommended, function (repository, callback) {
|
||||||
$.get('https://registry.hub.docker.com/v1/search?q=' + repository, function (data) {
|
$.get('https://registry.hub.docker.com/v1/search?q=' + repository, function (data) {
|
||||||
var results = data.results;
|
var results = data.results;
|
||||||
console.log(repository, data);
|
|
||||||
callback(null, _.find(results, function (r) {
|
callback(null, _.find(results, function (r) {
|
||||||
return r.name === repository;
|
return r.name === repository;
|
||||||
}));
|
}));
|
||||||
|
@ -287,8 +307,9 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
fetchLogs: function (name, callback) {
|
fetchLogs: function (name, callback) {
|
||||||
if (_logs[name]) {
|
if (_logs[name]) {
|
||||||
callback();
|
callback();
|
||||||
}
|
} else {
|
||||||
_logs[name] = [];
|
_logs[name] = [];
|
||||||
|
}
|
||||||
var index = 0;
|
var index = 0;
|
||||||
var self = this;
|
var self = this;
|
||||||
docker.client().getContainer(name).logs({
|
docker.client().getContainer(name).logs({
|
||||||
|
@ -297,6 +318,9 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
stderr: true,
|
stderr: true,
|
||||||
timestamps: true
|
timestamps: true
|
||||||
}, function (err, stream) {
|
}, function (err, stream) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
stream.setEncoding('utf8');
|
stream.setEncoding('utf8');
|
||||||
stream.on('data', function (buf) {
|
stream.on('data', function (buf) {
|
||||||
// Every other message is a header
|
// Every other message is a header
|
||||||
|
@ -304,11 +328,11 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
var time = buf.substr(0,buf.indexOf(' '));
|
var time = buf.substr(0,buf.indexOf(' '));
|
||||||
var msg = buf.substr(buf.indexOf(' ')+1);
|
var msg = buf.substr(buf.indexOf(' ')+1);
|
||||||
_logs[name].push(convert.toHtml(self._escapeHTML(msg)));
|
_logs[name].push(convert.toHtml(self._escapeHTML(msg)));
|
||||||
self.emit(self.SERVER_LOGS_EVENT, name);
|
|
||||||
}
|
}
|
||||||
index += 1;
|
index += 1;
|
||||||
});
|
});
|
||||||
stream.on('end', function (buf) {
|
stream.on('end', function (buf) {
|
||||||
|
self.emit(self.SERVER_LOGS_EVENT, name);
|
||||||
callback();
|
callback();
|
||||||
docker.client().getContainer(name).logs({
|
docker.client().getContainer(name).logs({
|
||||||
follow: true,
|
follow: true,
|
||||||
|
@ -317,6 +341,9 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
tail: 0
|
tail: 0
|
||||||
}, function (err, stream) {
|
}, function (err, stream) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
stream.setEncoding('utf8');
|
stream.setEncoding('utf8');
|
||||||
stream.on('data', function (buf) {
|
stream.on('data', function (buf) {
|
||||||
// Every other message is a header
|
// Every other message is a header
|
||||||
|
@ -345,10 +372,13 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
self._createPlaceholderContainer(imageName, containerName, function (err, container) {
|
self._createPlaceholderContainer(imageName, containerName, function (err, container) {
|
||||||
_containers[containerName] = container;
|
_containers[containerName] = container;
|
||||||
self.emit(self.CLIENT_CONTAINER_EVENT, containerName, 'create');
|
self.emit(self.CLIENT_CONTAINER_EVENT, containerName, 'create');
|
||||||
|
_muted[containerName] = true;
|
||||||
_progress[containerName] = 0;
|
_progress[containerName] = 0;
|
||||||
self._pullImage(repository, tag, function () {
|
self._pullImage(repository, tag, function () {
|
||||||
self._createContainer(imageName, containerName, function (err, container) {
|
self._createContainer(containerName, {Image: imageName}, function (err, container) {
|
||||||
delete _progress[containerName];
|
delete _progress[containerName];
|
||||||
|
_muted[containerName] = false;
|
||||||
|
self.emit(self.CLIENT_CONTAINER_EVENT, containerName);
|
||||||
});
|
});
|
||||||
}, function (progress) {
|
}, function (progress) {
|
||||||
_progress[containerName] = progress;
|
_progress[containerName] = progress;
|
||||||
|
@ -358,13 +388,61 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// If not then directly create the container
|
// If not then directly create the container
|
||||||
self._createContainer(imageName, containerName, function (err, container) {
|
self._createContainer(containerName, {Image: imageName}, function (err, container) {
|
||||||
self.emit(ContainerStore.CLIENT_CONTAINER_EVENT, containerName, 'create');
|
self.emit(ContainerStore.CLIENT_CONTAINER_EVENT, containerName, 'create');
|
||||||
callback(null, containerName);
|
callback(null, containerName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
updateContainer: function (name, data) {
|
||||||
|
_muted[name] = true;
|
||||||
|
var fullData = assign(_containers[name], data);
|
||||||
|
this._createContainer(name, fullData, function (err) {
|
||||||
|
this.emit(this.CLIENT_CONTAINER_EVENT, name);
|
||||||
|
_muted[name] = false;
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
remove: function (name, callback) {
|
||||||
|
var self = this;
|
||||||
|
var existing = docker.client().getContainer(name);
|
||||||
|
if (_containers[name].State.Paused) {
|
||||||
|
existing.unpause(function (err) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
existing.kill(function (err) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
existing.remove(function (err) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
existing.kill(function (err) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
existing.remove(function (err) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
containers: function() {
|
containers: function() {
|
||||||
return _containers;
|
return _containers;
|
||||||
},
|
},
|
||||||
|
@ -373,16 +451,7 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
||||||
},
|
},
|
||||||
sorted: function () {
|
sorted: function () {
|
||||||
return _.values(_containers).sort(function (a, b) {
|
return _.values(_containers).sort(function (a, b) {
|
||||||
var active = function (container) {
|
|
||||||
return container.State.Running || container.State.Restarting || container.State.Downloading;
|
|
||||||
};
|
|
||||||
if (active(a) && !active(b)) {
|
|
||||||
return -1;
|
|
||||||
} else if (!active(a) && active(b)) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return a.Name.localeCompare(b.Name);
|
return a.Name.localeCompare(b.Name);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
recommended: function () {
|
recommended: function () {
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
var ContainerUtil = {
|
||||||
|
env: function (container) {
|
||||||
|
if (!container || !container.Config || !container.Config.Env) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return _.object(container.Config.Env.map(function (env) {
|
||||||
|
var i = env.indexOf('=');
|
||||||
|
var splits = [env.slice(0, i), env.slice(i + 1)];
|
||||||
|
return splits;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ContainerUtil;
|
|
@ -52,7 +52,6 @@ var Containers = React.createClass({
|
||||||
sorted: ContainerStore.sorted()
|
sorted: ContainerStore.sorted()
|
||||||
});
|
});
|
||||||
if (status === 'create') {
|
if (status === 'create') {
|
||||||
console.log('transition');
|
|
||||||
this.transitionTo('container', {name: name});
|
this.transitionTo('container', {name: name});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,8 @@ var Radial = React.createClass({
|
||||||
}
|
}
|
||||||
var classes = React.addons.classSet({
|
var classes = React.addons.classSet({
|
||||||
'radial-progress': true,
|
'radial-progress': true,
|
||||||
'radial-spinner': this.props.spin
|
'radial-spinner': this.props.spin,
|
||||||
|
'radial-negative': this.props.error
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div className={classes} data-progress={this.props.progress}>
|
<div className={classes} data-progress={this.props.progress}>
|
||||||
|
|
|
@ -14,9 +14,10 @@ var ContainerStore = require('./ContainerStore.js');
|
||||||
var setupSteps = [
|
var setupSteps = [
|
||||||
{
|
{
|
||||||
run: function (callback, progressCallback) {
|
run: function (callback, progressCallback) {
|
||||||
|
console.log(util.supportDir());
|
||||||
var installed = virtualbox.installed();
|
var installed = virtualbox.installed();
|
||||||
if (!installed) {
|
if (!installed) {
|
||||||
util.download('https://s3.amazonaws.com/kite-installer/' + virtualbox.INSTALLER_FILENAME, path.join(process.cwd(), 'resources', virtualbox.INSTALLER_FILENAME), virtualbox.INSTALLER_CHECKSUM, function (err) {
|
util.download('https://s3.amazonaws.com/kite-installer/' + virtualbox.INSTALLER_FILENAME, path.join(util.supportDir(), virtualbox.INSTALLER_FILENAME), virtualbox.INSTALLER_CHECKSUM, function (err) {
|
||||||
if (err) {callback(err); return;}
|
if (err) {callback(err); return;}
|
||||||
virtualbox.install(function (err) {
|
virtualbox.install(function (err) {
|
||||||
if (!virtualbox.installed()) {
|
if (!virtualbox.installed()) {
|
||||||
|
@ -144,15 +145,24 @@ var Setup = React.createClass({
|
||||||
var radial;
|
var radial;
|
||||||
if (this.state.progress) {
|
if (this.state.progress) {
|
||||||
radial = <Radial progress={this.state.progress}/>;
|
radial = <Radial progress={this.state.progress}/>;
|
||||||
} else {
|
} else if (this.state.error) {
|
||||||
radial = <Radial spin="true" progress="92"/>;
|
radial = <Radial error={true} spin="true" progress="100"/>;
|
||||||
}
|
}
|
||||||
|
if (this.state.error) {
|
||||||
|
return (
|
||||||
|
<div className="setup">
|
||||||
|
{radial}
|
||||||
|
<p className="error">Error: {this.state.error}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className="setup">
|
<div className="setup">
|
||||||
{radial}
|
{radial}
|
||||||
<p>{this.state.message}</p>
|
<p>{this.state.message}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
componentWillMount: function () {
|
componentWillMount: function () {
|
||||||
this.setState({});
|
this.setState({});
|
||||||
|
@ -160,10 +170,12 @@ var Setup = React.createClass({
|
||||||
componentDidMount: function () {
|
componentDidMount: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.setup(function (err) {
|
this.setup(function (err) {
|
||||||
|
if (!err) {
|
||||||
boot2docker.ip(function (err, ip) {
|
boot2docker.ip(function (err, ip) {
|
||||||
docker.setHost(ip);
|
docker.setHost(ip);
|
||||||
self.transitionTo('containers');
|
self.transitionTo('containers');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setup: function (callback) {
|
setup: function (callback) {
|
||||||
|
@ -188,7 +200,7 @@ var Setup = React.createClass({
|
||||||
// if any of the steps fail
|
// if any of the steps fail
|
||||||
console.log('Kitematic setup failed at step ' + currentStep);
|
console.log('Kitematic setup failed at step ' + currentStep);
|
||||||
console.log(err);
|
console.log(err);
|
||||||
self.setState({error: err});
|
self.setState({error: err.message});
|
||||||
callback(err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
// Setup Finished
|
// Setup Finished
|
||||||
|
|
|
@ -54,7 +54,13 @@ var Boot2Docker = {
|
||||||
return path.join(process.cwd(), 'resources', 'boot2docker-' + this.version());
|
return path.join(process.cwd(), 'resources', 'boot2docker-' + this.version());
|
||||||
},
|
},
|
||||||
exists: function (callback) {
|
exists: function (callback) {
|
||||||
cmdExec([Boot2Docker.command(), 'info'], callback);
|
cmdExec([Boot2Docker.command(), 'info'], function (err, out) {
|
||||||
|
if (err) {
|
||||||
|
callback(null, false);
|
||||||
|
} else {
|
||||||
|
callback(null, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
status: function (callback) {
|
status: function (callback) {
|
||||||
cmdExec([Boot2Docker.command(), 'status'], function (err, out) {
|
cmdExec([Boot2Docker.command(), 'status'], function (err, out) {
|
||||||
|
|
Before Width: | Height: | Size: 726 B After Width: | Height: | Size: 705 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 340 B After Width: | Height: | Size: 347 B |
Before Width: | Height: | Size: 626 B After Width: | Height: | Size: 638 B |
After Width: | Height: | Size: 349 B |
After Width: | Height: | Size: 729 B |
Before Width: | Height: | Size: 571 B After Width: | Height: | Size: 535 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 466 B |
After Width: | Height: | Size: 1.0 KiB |
|
@ -3,6 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="main.css"/>
|
<link rel="stylesheet" href="main.css"/>
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src *; script-src 'self' http://localhost:35729; style-src 'self' 'unsafe-inline';">
|
<meta http-equiv="Content-Security-Policy" content="default-src *; script-src 'self' http://localhost:35729; style-src 'self' 'unsafe-inline';">
|
||||||
|
<title>Kitematic</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="main.js"></script>
|
<script src="main.js"></script>
|
||||||
|
|
|
@ -24,7 +24,7 @@ Bugsnag.releaseStage = process.env.NODE_ENV === 'development' ? 'development' :
|
||||||
Bugsnag.notifyReleaseStages = [];
|
Bugsnag.notifyReleaseStages = [];
|
||||||
Bugsnag.appVersion = app.getVersion();
|
Bugsnag.appVersion = app.getVersion();
|
||||||
|
|
||||||
if (window.location.hash === '#/') {
|
if (!window.location.hash.length || window.location.hash === '#/') {
|
||||||
router.run(function (Handler) {
|
router.run(function (Handler) {
|
||||||
React.render(<Handler/>, document.body);
|
React.render(<Handler/>, document.body);
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
//box-shadow: 0 3px 15px rgba(0, 0, 0, 0.2);
|
//box-shadow: 0 3px 15px rgba(0, 0, 0, 0.2);
|
||||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.10);
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.10);
|
||||||
border: none; //1px solid #ccc;
|
border: none; //1px solid #ccc;
|
||||||
height: 650px;
|
height: 610px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
.popover-content {
|
.popover-content {
|
||||||
max-height: 300px;
|
max-height: 160px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
.create {
|
.create {
|
||||||
flex: 1 auto;
|
flex: 1 auto;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
.btn {
|
/*.btn {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
padding: 4px 7px;
|
padding: 4px 7px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
top: 3px;
|
top: 3px;
|
||||||
left: 1px;
|
left: 1px;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
max-width: 280px;
|
||||||
|
|
||||||
&.sep {
|
&.sep {
|
||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
|
@ -329,25 +330,55 @@
|
||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details-logs {
|
.details-panel {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
h4 {
|
|
||||||
font-size: 14px;
|
|
||||||
margin-top: 16px;
|
|
||||||
margin-left: 40px;
|
|
||||||
}
|
|
||||||
.logs {
|
.logs {
|
||||||
-webkit-user-select: text;
|
-webkit-user-select: text;
|
||||||
font-family: Menlo;
|
font-family: Menlo;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 18px 45px;
|
padding: 18px 35px;
|
||||||
color: lighten(@gray-normal, 6%);
|
color: lighten(@gray-normal, 6%);
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
p {
|
p {
|
||||||
margin: 0 6px;
|
margin: 0 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.settings {
|
||||||
|
padding: 18px 35px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.env-vars-labels {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
color: @gray-lightest;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
.label-key {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 30px;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
.label-val {
|
||||||
|
display: inline-block;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.env-vars {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
.keyval-row {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
margin-right: 30px;
|
||||||
|
&.key {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
&.val {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
@import "clearsans.less";
|
@import "clearsans.less";
|
||||||
@import "theme.less";
|
@import "theme.less";
|
||||||
@import "icons.less";
|
@import "icons.less";
|
||||||
@import "icons-filled.less";
|
|
||||||
@import "retina.less";
|
@import "retina.less";
|
||||||
@import "setup.less";
|
@import "setup.less";
|
||||||
@import "radial.less";
|
@import "radial.less";
|
||||||
|
@ -42,11 +41,14 @@ html, body {
|
||||||
width: 7px;
|
width: 7px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: rgba(0,0,0,0.2);
|
background-color: rgba(0,0,0,0.2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0,0,0,0.25);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover {
|
.popover {
|
||||||
font-family: 'Clear Sans', sans-serif;
|
font-family: 'Clear Sans', sans-serif;
|
||||||
|
|
||||||
color: @gray-normal;
|
color: @gray-normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
|
&.error {
|
||||||
|
color: @brand-danger;
|
||||||
|
}
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
@import "bootstrap/variables.less";
|
@import "bootstrap/variables.less";
|
||||||
@import "bootstrap/mixins.less";
|
@import "bootstrap/mixins.less";
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 14px;
|
||||||
|
color: @gray-darkest;
|
||||||
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
@ -12,6 +16,30 @@ h4 {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popover-content {
|
||||||
|
color: @gray-normal;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
&.line {
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid @gray-lightest;
|
||||||
|
color: @gray-normal;
|
||||||
|
font-weight: 300;
|
||||||
|
padding: 5px;
|
||||||
|
transition: all 0.1s;
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
border-bottom: 1px solid @brand-action;
|
||||||
|
}
|
||||||
|
&::-webkit-input-placeholder {
|
||||||
|
color: #ddd;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Buttons
|
// Buttons
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
@ -63,10 +91,17 @@ h4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-group {
|
.btn-group {
|
||||||
|
&.tabs {
|
||||||
|
.btn {
|
||||||
|
padding: 6px 14px 6px 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
.btn {
|
.btn {
|
||||||
.icon-dropdown {
|
.icon-dropdown {
|
||||||
&.icon:before {
|
&.icon:before {
|
||||||
top: 7px;
|
position: relative;
|
||||||
|
font-size: 10px;
|
||||||
|
top: -2px;
|
||||||
margin-left: 0px;
|
margin-left: 0px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
@ -88,6 +123,13 @@ h4 {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
height: 22px;
|
||||||
|
.icon {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -4px;
|
top: -4px;
|
||||||
|
@ -95,6 +137,14 @@ h4 {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-dropdown {
|
||||||
|
&.icon:before {
|
||||||
|
font-size: 10px;
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
@ -112,12 +162,22 @@ h4 {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.only-icon {
|
||||||
|
padding: 6px 7px 6px 7px;
|
||||||
|
&.small {
|
||||||
|
padding: 2px 5px 3px 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the mixin to the buttons
|
// Apply the mixin to the buttons
|
||||||
.btn-action {
|
.btn-action {
|
||||||
.btn-styles(@brand-action);
|
.btn-styles(@brand-action);
|
||||||
}
|
}
|
||||||
|
.btn-positive {
|
||||||
|
.btn-styles(@brand-positive);
|
||||||
|
}
|
||||||
.btn-default { .btn-styles(@btn-default-bg); }
|
.btn-default { .btn-styles(@btn-default-bg); }
|
||||||
.btn-primary { .btn-styles(@btn-primary-bg); }
|
.btn-primary { .btn-styles(@btn-primary-bg); }
|
||||||
.btn-success { .btn-styles(@btn-success-bg); }
|
.btn-success { .btn-styles(@btn-success-bg); }
|
||||||
|
|
16
app/util.js
|
@ -6,9 +6,23 @@ var progress = require('request-progress');
|
||||||
var exec = require('exec');
|
var exec = require('exec');
|
||||||
|
|
||||||
var Util = {
|
var Util = {
|
||||||
|
supportDir: function (callback) {
|
||||||
|
var dirs = ['Application\ Support', 'Kitematic'];
|
||||||
|
var acc = process.env.HOME;
|
||||||
|
dirs.forEach(function (d) {
|
||||||
|
acc = path.join(acc, d);
|
||||||
|
if (!fs.existsSync(acc)) {
|
||||||
|
fs.mkdirSync(acc);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
download: function (url, filename, checksum, callback, progressCallback) {
|
download: function (url, filename, checksum, callback, progressCallback) {
|
||||||
var doDownload = function () {
|
var doDownload = function () {
|
||||||
progress(request(url), {
|
progress(request({
|
||||||
|
uri: url,
|
||||||
|
rejectUnauthorized: false
|
||||||
|
}), {
|
||||||
throttle: 250
|
throttle: 250
|
||||||
}).on('progress', function (state) {
|
}).on('progress', function (state) {
|
||||||
progressCallback(state.percent);
|
progressCallback(state.percent);
|
||||||
|
|
|
@ -2,6 +2,7 @@ var fs = require('fs');
|
||||||
var exec = require('exec');
|
var exec = require('exec');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
var util = require('./util');
|
||||||
|
|
||||||
var VirtualBox = {
|
var VirtualBox = {
|
||||||
REQUIRED_VERSION: '4.3.18',
|
REQUIRED_VERSION: '4.3.18',
|
||||||
|
@ -16,7 +17,7 @@ var VirtualBox = {
|
||||||
},
|
},
|
||||||
install: function (callback) {
|
install: function (callback) {
|
||||||
// -W waits for the process to close before finishing.
|
// -W waits for the process to close before finishing.
|
||||||
exec('open -W ' + path.join(process.cwd(), 'resources', this.INSTALLER_FILENAME).replace(' ', '\\ '), function (stderr, stdout, code) {
|
exec('open -W ' + path.join(util.supportDir(), this.INSTALLER_FILENAME).replace(' ', '\\ '), function (stderr, stdout, code) {
|
||||||
if (code) {
|
if (code) {
|
||||||
callback(stderr);
|
callback(stderr);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
"minimist": "^1.1.0",
|
"minimist": "^1.1.0",
|
||||||
"moment": "2.8.1",
|
"moment": "2.8.1",
|
||||||
"node-uuid": "1.4.1",
|
"node-uuid": "1.4.1",
|
||||||
|
"object-assign": "^2.0.0",
|
||||||
"open": "0.0.5",
|
"open": "0.0.5",
|
||||||
"react": "^0.12.2",
|
"react": "^0.12.2",
|
||||||
"react-bootstrap": "^0.13.2",
|
"react-bootstrap": "^0.13.2",
|
||||||
|
|
|
@ -9,8 +9,6 @@ end try
|
||||||
return doesExist
|
return doesExist
|
||||||
EOF`
|
EOF`
|
||||||
|
|
||||||
echo $ITERM_EXISTS
|
|
||||||
|
|
||||||
if [ $ITERM_EXISTS == "true" ]; then
|
if [ $ITERM_EXISTS == "true" ]; then
|
||||||
osascript > /dev/null <<EOF
|
osascript > /dev/null <<EOF
|
||||||
tell application "iTerm"
|
tell application "iTerm"
|
||||||
|
@ -19,14 +17,14 @@ tell application "iTerm"
|
||||||
tell the first terminal
|
tell the first terminal
|
||||||
launch session "Default Session"
|
launch session "Default Session"
|
||||||
tell the last session
|
tell the last session
|
||||||
write text "clear && $*"
|
write text "bash -c \"clear && $*\""
|
||||||
end tell
|
end tell
|
||||||
end tell
|
end tell
|
||||||
on error
|
on error
|
||||||
tell (make new terminal)
|
tell (make new terminal)
|
||||||
launch session "Default Session"
|
launch session "Default Session"
|
||||||
tell the last session
|
tell the last session
|
||||||
write text "clear && $*"
|
write text "bash -c \"clear && $*\""
|
||||||
end tell
|
end tell
|
||||||
end tell
|
end tell
|
||||||
end try
|
end try
|
||||||
|
@ -39,7 +37,7 @@ tell application "Terminal" to activate
|
||||||
delay 0.4
|
delay 0.4
|
||||||
tell application "System Events" to keystroke "t" using command down
|
tell application "System Events" to keystroke "t" using command down
|
||||||
tell application "Terminal"
|
tell application "Terminal"
|
||||||
do script "clear && $*" in window 1
|
do script "bash -c \"clear && $*\"" in window 1
|
||||||
end tell
|
end tell
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
|