Merge remote-tracking branch 'origin/sean' into jmorgan-container-merge
Conflicts: app/ContainerDetails.react.js app/ContainerModal.react.js app/styles/main.less
|
@ -3,6 +3,7 @@ var $ = require('jquery');
|
|||
var React = require('react/addons');
|
||||
var Router = require('react-router');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var ContainerUtil = require('./ContainerUtil');
|
||||
var docker = require('./docker');
|
||||
var exec = require('exec');
|
||||
var boot2docker = require('./boot2docker');
|
||||
|
@ -22,22 +23,28 @@ var ContainerDetails = React.createClass({
|
|||
getInitialState: function () {
|
||||
return {
|
||||
logs: [],
|
||||
page: this.PAGE_LOGS
|
||||
page: this.PAGE_LOGS,
|
||||
env: {},
|
||||
pendingEnv: {}
|
||||
};
|
||||
},
|
||||
componentWillReceiveProps: function () {
|
||||
this.setState({
|
||||
page: this.PAGE_LOGS
|
||||
});
|
||||
ContainerStore.fetchLogs(this.getParams().name, function () {
|
||||
this.updateLogs();
|
||||
}.bind(this));
|
||||
// active container changes
|
||||
if (this.state.page === this.PAGE_SETTINGS) {
|
||||
|
||||
}
|
||||
console.log(this.props.container);
|
||||
this.init();
|
||||
},
|
||||
componentWillMount: function () {
|
||||
this.init();
|
||||
},
|
||||
componentDidMount: function () {
|
||||
ContainerStore.on(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
|
||||
ContainerStore.on(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
// app close
|
||||
ContainerStore.removeListener(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
|
||||
ContainerStore.removeListener(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
},
|
||||
|
@ -52,6 +59,15 @@ var ContainerDetails = React.createClass({
|
|||
}
|
||||
this._oldHeight = parent[0].scrollHeight - parent.height();
|
||||
},
|
||||
init: function () {
|
||||
this.setState({
|
||||
page: this.PAGE_LOGS,
|
||||
env: ContainerUtil.env(ContainerStore.container(this.getParams().name))
|
||||
});
|
||||
ContainerStore.fetchLogs(this.getParams().name, function () {
|
||||
this.updateLogs();
|
||||
}.bind(this));
|
||||
},
|
||||
updateLogs: function (name) {
|
||||
if (name && name !== this.getParams().name) {
|
||||
return;
|
||||
|
@ -78,7 +94,7 @@ var ContainerDetails = React.createClass({
|
|||
page: this.PAGE_SETTINGS
|
||||
});
|
||||
},
|
||||
handleClick: function (name) {
|
||||
handleView: function () {
|
||||
var container = this.props.container;
|
||||
boot2docker.ip(function (err, ip) {
|
||||
var ports = _.map(container.NetworkSettings.Ports, function (value, key) {
|
||||
|
@ -102,6 +118,46 @@ var ContainerDetails = React.createClass({
|
|||
});
|
||||
});
|
||||
},
|
||||
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();
|
||||
envVarList.push(key + '=' + val);
|
||||
});
|
||||
console.log(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 () {
|
||||
var container = this.props.container;
|
||||
var name = container.Name.replace('/', '');
|
||||
ContainerStore.remove(name, function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
var self = this;
|
||||
|
||||
|
@ -135,6 +191,25 @@ var ContainerDetails = React.createClass({
|
|||
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;
|
||||
if (this.props.container.State.Downloading) {
|
||||
body = (
|
||||
|
@ -145,7 +220,7 @@ var ContainerDetails = React.createClass({
|
|||
} else {
|
||||
if (this.state.page === this.PAGE_LOGS) {
|
||||
body = (
|
||||
<div className="details-logs">
|
||||
<div className="details-panel">
|
||||
<div className="logs">
|
||||
{logs}
|
||||
</div>
|
||||
|
@ -153,16 +228,33 @@ var ContainerDetails = React.createClass({
|
|||
);
|
||||
} else {
|
||||
body = (
|
||||
<div className="details-logs">
|
||||
<div className="details-panel">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var name = this.props.container.Name;
|
||||
var image = this.props.container.Config.Image;
|
||||
var disabledClass = '';
|
||||
if (!this.props.container.State.Running) {
|
||||
disabledClass = 'disabled';
|
||||
|
@ -201,20 +293,20 @@ var ContainerDetails = React.createClass({
|
|||
<div className="details">
|
||||
<div className="details-header">
|
||||
<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 className="details-header-actions">
|
||||
<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="btn btn-action with-icon dropdown-toggle"><span className="icon-dropdown icon icon-arrow-37"></span></a>
|
||||
</div>
|
||||
<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 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 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.handleView}><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>
|
||||
|
|
|
@ -10,6 +10,8 @@ var MenuItem = require('react-bootstrap/MenuItem');
|
|||
|
||||
var RetinaImage = require('react-retina-image');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var OverlayTrigger = require('react-bootstrap/OverlayTrigger');
|
||||
var Popover = require('react-bootstrap/Popover');
|
||||
|
||||
var ContainerModal = React.createClass({
|
||||
_searchRequest: null,
|
||||
|
@ -152,8 +154,7 @@ var ContainerModal = React.createClass({
|
|||
<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 dropdown-toggle" onClick={self.handleDropdownClick.bind(self, r.name)} data-name={r.name}>
|
||||
<span className="caret"></span>
|
||||
<span className="sr-only">Toggle Dropdown</span>
|
||||
<span className="icon-dropdown icon icon-arrow-37"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,6 +6,7 @@ var Convert = require('ansi-to-html');
|
|||
var convert = new Convert();
|
||||
var docker = require('./docker');
|
||||
var registry = require('./registry');
|
||||
var ContainerUtil = require('./ContainerUtil');
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
|
||||
|
@ -104,16 +105,12 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
|||
div.appendChild(text);
|
||||
return div.innerHTML;
|
||||
},
|
||||
_createContainer: function (image, name, callback) {
|
||||
_createContainer: function (name, data, callback) {
|
||||
var existing = docker.client().getContainer(name);
|
||||
var self = this;
|
||||
data.name = name;
|
||||
existing.remove(function (err, data) {
|
||||
docker.client().createContainer({
|
||||
Image: image,
|
||||
Tty: false,
|
||||
name: name,
|
||||
User: 'root'
|
||||
}, function (err, container) {
|
||||
docker.client().createContainer(data, function (err, container) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
|
@ -130,6 +127,79 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
|||
});
|
||||
});
|
||||
},
|
||||
updateContainer: function (name, data) {
|
||||
var fullData = assign(this._containers[name], data);
|
||||
this._createContainer(name, fullData, function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
},
|
||||
rename: function (name, newName, callback) {
|
||||
var existing = docker.client().getContainer(name);
|
||||
var existingImage = existing.Image;
|
||||
var self = this;
|
||||
existing.remove(function (err, data) {
|
||||
docker.client().createContainer({
|
||||
Image: existingImage,
|
||||
Tty: false,
|
||||
name: newName,
|
||||
User: 'root'
|
||||
}, function (err, container) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
container.start({
|
||||
PublishAllPorts: true
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
self.fetchContainer(newName, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
_createPlaceholderContainer: function (imageName, name, callback) {
|
||||
var self = this;
|
||||
this._pullScratchImage(function (err) {
|
||||
|
@ -231,7 +301,7 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
|||
container.Name = container.Name.replace('/', '');
|
||||
|
||||
// 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.KitematicDownloadingImage = env.KITEMATIC_DOWNLOADING_IMAGE;
|
||||
|
||||
|
@ -373,16 +443,7 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
|||
},
|
||||
sorted: function () {
|
||||
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);
|
||||
}
|
||||
});
|
||||
},
|
||||
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;
|
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>
|
||||
<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';">
|
||||
<title>Kitematic</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="main.js"></script>
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
.create {
|
||||
flex: 1 auto;
|
||||
text-align: right;
|
||||
.btn {
|
||||
/*.btn {
|
||||
margin-top: 4px;
|
||||
padding: 4px 7px;
|
||||
font-size: 16px;
|
||||
|
@ -51,7 +51,7 @@
|
|||
top: 3px;
|
||||
left: 1px;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,25 +329,55 @@
|
|||
width: 300px;
|
||||
}
|
||||
|
||||
.details-logs {
|
||||
.details-panel {
|
||||
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: 18px 45px;
|
||||
padding: 18px 35px;
|
||||
color: lighten(@gray-normal, 6%);
|
||||
white-space: pre-wrap;
|
||||
p {
|
||||
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 "theme.less";
|
||||
@import "icons.less";
|
||||
@import "icons-filled.less";
|
||||
@import "retina.less";
|
||||
@import "setup.less";
|
||||
@import "radial.less";
|
||||
|
@ -42,11 +41,14 @@ html, body {
|
|||
width: 7px;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0,0,0,0.25);
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
font-family: 'Clear Sans', sans-serif;
|
||||
|
||||
color: @gray-normal;
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
@import "bootstrap/variables.less";
|
||||
@import "bootstrap/mixins.less";
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
color: @gray-darkest;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 13px;
|
||||
|
@ -12,6 +16,30 @@ h4 {
|
|||
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
|
||||
// --------------------------------------------------
|
||||
|
@ -63,10 +91,17 @@ h4 {
|
|||
}
|
||||
|
||||
.btn-group {
|
||||
&.tabs {
|
||||
.btn {
|
||||
padding: 6px 14px 6px 14px;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
.icon-dropdown {
|
||||
&.icon:before {
|
||||
top: 7px;
|
||||
position: relative;
|
||||
font-size: 10px;
|
||||
top: -2px;
|
||||
margin-left: 0px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
@ -88,6 +123,13 @@ h4 {
|
|||
height: 32px;
|
||||
cursor: default;
|
||||
|
||||
&.small {
|
||||
height: 22px;
|
||||
.icon {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
top: -4px;
|
||||
|
@ -95,6 +137,14 @@ h4 {
|
|||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.icon-dropdown {
|
||||
&.icon:before {
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
|
@ -112,12 +162,22 @@ h4 {
|
|||
box-shadow: none;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
&.only-icon {
|
||||
padding: 6px 7px 6px 7px;
|
||||
&.small {
|
||||
padding: 2px 5px 3px 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the mixin to the buttons
|
||||
.btn-action {
|
||||
.btn-styles(@brand-action);
|
||||
}
|
||||
.btn-positive {
|
||||
.btn-styles(@brand-positive);
|
||||
}
|
||||
.btn-default { .btn-styles(@btn-default-bg); }
|
||||
.btn-primary { .btn-styles(@btn-primary-bg); }
|
||||
.btn-success { .btn-styles(@btn-success-bg); }
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"minimist": "^1.1.0",
|
||||
"moment": "2.8.1",
|
||||
"node-uuid": "1.4.1",
|
||||
"object-assign": "^2.0.0",
|
||||
"open": "0.0.5",
|
||||
"react": "^0.12.2",
|
||||
"react-bootstrap": "^0.13.2",
|
||||
|
|