Change unique container key to name from Id

This commit is contained in:
Jeffrey Morgan 2015-01-19 12:19:40 -05:00
parent fa41982603
commit f34e23b7a0
8 changed files with 197 additions and 369 deletions

View File

@ -8,8 +8,8 @@ var Link = Router.Link;
var RouteHandler = Router.RouteHandler; var RouteHandler = Router.RouteHandler;
var Convert = require('ansi-to-html'); var Convert = require('ansi-to-html');
var convert = new Convert(); var convert = new Convert();
var ContainerStore = require('./ContainerStore.js'); var ContainerStore = require('./ContainerStore');
var docker = require('./docker.js'); var docker = require('./docker');
var ContainerDetails = React.createClass({ var ContainerDetails = React.createClass({
mixins: [Router.State], mixins: [Router.State],
@ -18,12 +18,12 @@ var ContainerDetails = React.createClass({
logs: [] logs: []
}; };
}, },
componentWillMount: function () { componentWillReceiveProps: function () {
this.update(); this.update();
var self = this; var self = this;
var logs = []; var logs = [];
var index = 0; var index = 0;
docker.client().getContainer(this.getParams().Id).logs({ docker.client().getContainer(this.getParams().name).logs({
follow: false, follow: false,
stdout: true, stdout: true,
timestamps: true timestamps: true
@ -40,7 +40,50 @@ var ContainerDetails = React.createClass({
}); });
stream.on('end', function (buf) { stream.on('end', function (buf) {
self.setState({logs: logs}); self.setState({logs: logs});
docker.client().getContainer(self.getParams().Id).logs({ docker.client().getContainer(self.getParams().name).logs({
follow: true,
stdout: 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;
});
});
});
});
},
componentWillMount: function () {
this.update();
var self = this;
var logs = [];
var index = 0;
docker.client().getContainer(this.getParams().name).logs({
follow: false,
stdout: 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, follow: true,
stdout: true, stdout: true,
timestamps: true, timestamps: true,
@ -68,9 +111,9 @@ var ContainerDetails = React.createClass({
ContainerStore.removeChangeListener(this.update); ContainerStore.removeChangeListener(this.update);
}, },
update: function () { update: function () {
var containerId = this.getParams().Id; var containerName = this.getParams().name;
this.setState({ this.setState({
container: ContainerStore.containers()[containerId] container: ContainerStore.containers()[containerName]
}); });
}, },
_escapeHTML: function (html) { _escapeHTML: function (html) {

View File

@ -3,7 +3,7 @@ var Router = require('react-router');
var Modal = require('react-bootstrap/Modal'); var Modal = require('react-bootstrap/Modal');
var RetinaImage = require('react-retina-image'); var RetinaImage = require('react-retina-image');
var $ = require('jquery'); var $ = require('jquery');
var ContainerStore = require('./ContainerStore.js'); var ContainerStore = require('./ContainerStore');
var ContainerModal = React.createClass({ var ContainerModal = React.createClass({
getInitialState: function () { getInitialState: function () {
@ -28,7 +28,6 @@ var ContainerModal = React.createClass({
if (query === this.state.query) { if (query === this.state.query) {
return; return;
} }
clearTimeout(this.timeout); clearTimeout(this.timeout);
var self = this; var self = this;
this.timeout = setTimeout(function () { this.timeout = setTimeout(function () {
@ -37,7 +36,8 @@ var ContainerModal = React.createClass({
}, },
handleClick: function (event) { handleClick: function (event) {
var name = event.target.getAttribute('name'); var name = event.target.getAttribute('name');
ContainerStore.create(name); ContainerStore.create(name, 'latest', function (err, containerName) {
});
}, },
render: function () { render: function () {
var top = this.state.results.splice(0, 7); var top = this.state.results.splice(0, 7);

View File

@ -42,6 +42,7 @@ var ContainerStore = assign(EventEmitter.prototype, {
docker.client().getEvents(function (err, stream) { docker.client().getEvents(function (err, stream) {
stream.setEncoding('utf8'); stream.setEncoding('utf8');
stream.on('data', function (data) { stream.on('data', function (data) {
console.log(data);
// TODO: Make // TODO: Make
self.update(function (err) { self.update(function (err) {
@ -69,10 +70,9 @@ var ContainerStore = assign(EventEmitter.prototype, {
} }
var containers = {}; var containers = {};
results.map(function (r) { results.map(function (r) {
containers[r.Id] = r; containers[r.Name.replace('/', '')] = r;
}); });
self._containers = containers; self._containers = containers;
console.log(containers);
self.emit('change'); self.emit('change');
callback(null); callback(null);
}); });
@ -157,6 +157,10 @@ var ContainerStore = assign(EventEmitter.prototype, {
} }
} }
}, },
// Returns all shoes
containers: function() {
return this._containers;
},
create: function (repository, tag, callback) { create: function (repository, tag, callback) {
console.log('create', repository, tag); console.log('create', repository, tag);
@ -182,6 +186,8 @@ var ContainerStore = assign(EventEmitter.prototype, {
} }
console.log('Placeholder container created.'); console.log('Placeholder container created.');
docker.client().pull(imageName, function (err, stream) { docker.client().pull(imageName, function (err, stream) {
console.log(containerName);
callback(null, containerName);
stream.setEncoding('utf8'); stream.setEncoding('utf8');
stream.on('data', function (data) { stream.on('data', function (data) {
console.log(data); console.log(data);
@ -197,6 +203,7 @@ 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 () { self._createContainer(imageName, containerName, function () {
callback(null, containerName);
console.log('done'); console.log('done');
}); });
} }
@ -205,19 +212,15 @@ var ContainerStore = assign(EventEmitter.prototype, {
// Pull image // Pull image
// When image is done pulling then // When image is done pulling then
}, },
logs: function (containerName) {
// Returns all shoes return logs[containerId];
containers: function() {
return this._containers;
}, },
addChangeListener: function(callback) { addChangeListener: function(callback) {
this.on('change', callback); this.on('change', callback);
}, },
removeChangeListener: function(callback) { removeChangeListener: function(callback) {
this.removeListener('change', callback); this.removeListener('change', callback);
} },
}); });
module.exports = ContainerStore; module.exports = ContainerStore;

View File

@ -1,20 +1,18 @@
var React = require('react'); var React = require('react/addons');
var Router = require('react-router'); var Router = require('react-router');
var Modal = require('react-bootstrap/Modal'); var Modal = require('react-bootstrap/Modal');
var RetinaImage = require('react-retina-image'); var RetinaImage = require('react-retina-image');
var ModalTrigger = require('react-bootstrap/ModalTrigger'); var ModalTrigger = require('react-bootstrap/ModalTrigger');
var ContainerModal = require('./ContainerModal.react.js'); var ContainerModal = require('./ContainerModal.react');
var ContainerStore = require('./ContainerStore.js'); var ContainerStore = require('./ContainerStore');
var Route = Router.Route; var Header = require('./Header.react');
var NotFoundRoute = Router.NotFoundRoute; var async = require('async');
var DefaultRoute = Router.DefaultRoute; var _ = require('underscore');
var docker = require('./docker');
var Link = Router.Link; var Link = Router.Link;
var RouteHandler = Router.RouteHandler; var RouteHandler = Router.RouteHandler;
var Navigation= Router.Navigation; var Navigation= Router.Navigation;
var Header = require('./Header.react.js');
var async = require('async');
var _ = require('underscore');
var docker = require('./docker.js');
var ContainerList = React.createClass({ var ContainerList = React.createClass({
mixins: [Navigation], mixins: [Navigation],
@ -22,15 +20,12 @@ var ContainerList = React.createClass({
return { return {
containers: [] containers: []
}; };
},
handleClick: function () {
}, },
componentDidMount: function () { componentDidMount: function () {
this.update(); this.update();
ContainerStore.addChangeListener(this.update); ContainerStore.addChangeListener(this.update);
if (this.state.containers.length > 0) { if (this.state.active) {
this.transitionTo('container', {Id: this.state.containers[0].Id}); this.transitionTo('container', {name: this.state.active});
} }
}, },
componentWillMount: function () { componentWillMount: function () {
@ -43,8 +38,16 @@ var ContainerList = React.createClass({
var containers = _.values(ContainerStore.containers()).sort(function (a, b) { var containers = _.values(ContainerStore.containers()).sort(function (a, b) {
return a.Name.localeCompare(b.Name); return a.Name.localeCompare(b.Name);
}); });
var state = {};
if (!this.state.active && containers.length > 0) {
state.active = containers[0].Name.replace('/', '');
}
state.containers = containers;
this.setState(state);
},
handleClick: function (containerId) {
this.setState({ this.setState({
containers: containers active: containerId
}); });
}, },
render: function () { render: function () {
@ -85,8 +88,9 @@ var ContainerList = React.createClass({
} else { } else {
state = <div className="state state-stopped"></div>; state = <div className="state state-stopped"></div>;
} }
return ( return (
<Link key={container.Id} to="container" params={{Id: container.Id}} onClick={this.handleClick}> <Link key={container.Name.replace('/', '')} to="container" params={{name: container.Name.replace('/', '')}} onClick={self.handleClick.bind(self, container.Id)}>
<li> <li>
{state} {state}
<div className="info"> <div className="info">
@ -126,9 +130,6 @@ var Containers = React.createClass({
}); });
} }
}, },
handleClick: function () {
ContainerStore.create('dockerfile/ghost', 'latest', 'testghost');
},
render: function () { render: function () {
var sidebarHeaderClass = 'sidebar-header'; var sidebarHeaderClass = 'sidebar-header';
if (this.state.sidebarOffset) { if (this.state.sidebarOffset) {
@ -140,7 +141,7 @@ var Containers = React.createClass({
<div className="containers-body"> <div className="containers-body">
<div className="sidebar"> <div className="sidebar">
<section className={sidebarHeaderClass}> <section className={sidebarHeaderClass}>
<h3 onClick={this.handleClick}>containers</h3> <h3>containers</h3>
<div className="create"> <div className="create">
<ModalTrigger modal={<ContainerModal/>}> <ModalTrigger modal={<ContainerModal/>}>
<div className="wrapper"> <div className="wrapper">

View File

@ -1,12 +1,6 @@
var React = require('react'); var React = require('react');
var Router = require('react-router'); var Router = require('react-router');
var RetinaImage = require('react-retina-image'); var RetinaImage = require('react-retina-image');
var Route = Router.Route;
var NotFoundRoute = Router.NotFoundRoute;
var DefaultRoute = Router.DefaultRoute;
var Link = Router.Link;
var RouteHandler = Router.RouteHandler;
var Raven = require('raven'); var Raven = require('raven');
var async = require('async'); var async = require('async');
var docker = require('./docker.js'); var docker = require('./docker.js');
@ -15,7 +9,13 @@ var Setup = require('./Setup.react');
var Containers = require('./Containers.react'); var Containers = require('./Containers.react');
var ContainerDetails = require('./ContainerDetails.react'); var ContainerDetails = require('./ContainerDetails.react');
var ContainerStore = require('./ContainerStore.js'); var ContainerStore = require('./ContainerStore.js');
var Radial = require('./Radial.react'); var Radial = require('./Radial.react.js');
var Route = Router.Route;
var NotFoundRoute = Router.NotFoundRoute;
var DefaultRoute = Router.DefaultRoute;
var Link = Router.Link;
var RouteHandler = Router.RouteHandler;
var NoContainers = React.createClass({ var NoContainers = React.createClass({
render: function () { render: function () {
@ -38,7 +38,7 @@ var App = React.createClass({
var routes = ( var routes = (
<Route name="app" path="/" handler={App}> <Route name="app" path="/" handler={App}>
<Route name="containers" handler={Containers}> <Route name="containers" handler={Containers}>
<Route name="container" path=":Id" handler={ContainerDetails}> <Route name="container" path=":name" handler={ContainerDetails}>
</Route> </Route>
<DefaultRoute handler={NoContainers}/> <DefaultRoute handler={NoContainers}/>
</Route> </Route>
@ -48,17 +48,19 @@ var routes = (
</Route> </Route>
); );
Router.run(routes, function (Handler) { boot2docker.ip(function (err, ip) {
boot2docker.ip(function (err, ip) { if (!err) {
if (!err) { docker.setHost(ip);
docker.setHost(ip); ContainerStore.init(function () {
ContainerStore.init(function () { Router.run(routes, function (Handler) {
React.render(<Handler/>, document.body); React.render(<Handler/>, document.body);
}); });
} else { });
} else {
Router.run(routes, function (Handler) {
React.render(<Handler/>, document.body); React.render(<Handler/>, document.body);
} });
}); }
}); });
if (process.env.NODE_ENV !== 'development') { if (process.env.NODE_ENV !== 'development') {

View File

@ -1,71 +1,3 @@
@import "bootstrap/bootstrap.less";
@import "clearsans.less";
@import "theme.less";
@import "icons.less";
@import "retina.less";
@import "setup.less";
@import "radial.less";
.buttons {
display: inline-block;
position: relative;
top: 16px;
left: 20px;
&:hover {
.button-minimize.enabled {
.at2x('minimize.png', 10px, 10px);
}
.button-close.enabled {
.at2x('close.png', 10px, 10px);
}
.button-fullscreen.enabled {
.at2x('fullscreen.png', 10px, 10px);
}
.button-fullscreenclose.enabled {
.at2x('fullscreenclose.png', 10px, 10px);
}
}
.button {
box-sizing: border-box;
display: inline-block;
background: white;
margin-right: 9px;
height: 12px;
width: 12px;
border: 1px solid #CCD3D5;
border-radius: 6px;
box-shadow: 0px 1px 1px 0px rgba(234,234,234,0.50);
-webkit-app-region: no-drag;
&.disabled {
border: 1px solid #E8EEEF;
}
&.enabled:hover {
box-shadow: 0px 1px 1px 0px rgba(195,198,201,0.50);
}
&.enabled:hover:active {
cursor: default;
-webkit-filter: brightness(92%);
}
}
}
.header {
min-width: 100%;
flex: 0;
min-height: 48px;
-webkit-app-region: drag;
-webkit-user-select: none;
&.no-drag {
-webkit-app-region: no-drag;
}
}
.containers { .containers {
height: 100%; height: 100%;
display: flex; display: flex;
@ -78,20 +10,21 @@
.sidebar { .sidebar {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 240px; min-width: 260px;
margin: 0; margin: 0;
box-sizing: border-box; box-sizing: border-box;
border-right: 1px solid #eee; border-right: 1px solid #eee;
.sidebar-header { .sidebar-header {
flex: 0 auto; flex: 0 auto;
min-width: 240px; min-width: 260px;
display: flex; display: flex;
border-bottom: 1px solid transparent; border-bottom: 1px solid transparent;
transition: border-bottom 0.25s; transition: border-bottom 0.25s;
&.sep { &.sep {
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.03);
} }
h3 { h3 {
@ -148,7 +81,7 @@
ul { ul {
padding: 0; padding: 0;
margin: 0; margin: 0;
min-width: 240px; min-width: 260px;
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
@ -163,23 +96,18 @@
flex-shrink: 0; flex-shrink: 0;
cursor: default; cursor: default;
/*&:hover { &.active {
background: #eee;
border-bottom: none;
&:hover {
}
}
&:hover {
text-decoration: none; text-decoration: none;
cursor: default; cursor: default;
background: @brand-primary; }
li {
border-bottom: none;
}
li > .info > .name {
color: #fff;
}
li > .info > .image {
color: #fff;
}
}*/
&:focus { &:focus {
text-decoration: none; text-decoration: none;
} }
@ -187,10 +115,8 @@
li { li {
vertical-align: middle; vertical-align: middle;
margin: 14px 24px 0px;
border-bottom: 1px solid #efefef;
padding-bottom: 14px; padding-bottom: 14px;
margin: 16px 24px 0px;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -198,18 +124,18 @@
.info { .info {
font-size: 13px; font-size: 13px;
margin-left: 12px; margin-left: 16px;
.name { .name {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
font-size: 13px; font-size: 14px;
font-weight: 400; font-weight: 400;
color: #555; color: #555;
} }
.image { .image {
color: #999; color: #999;
font-size: 10px; font-size: 12px;
font-weight: 400; font-weight: 400;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
@ -218,7 +144,7 @@
} }
.state { .state {
margin-top: 8px; margin-top: 9px;
display: inline-block; display: inline-block;
position: relative; position: relative;
min-width: 20px; min-width: 20px;
@ -349,200 +275,3 @@
} }
} }
} }
html, body {
height: 100%;
width: 100%;
overflow: hidden;
-webkit-font-smoothing: antialiased;
user-select: none;
font-family: 'Clear Sans', sans-serif;
}
::-webkit-scrollbar {
width: 13px;
}
::-webkit-scrollbar-track {
margin: 3px;
-webkit-border-radius: 5px;
border-radius: 5px;
background: none;
}
::-webkit-scrollbar-thumb {
border: 3px solid rgba(0, 0, 0, 0);
background-clip: padding-box;
width: 7px;
border-radius: 8px;
background-color: rgba(0,0,0,0.2);
}
.create-modal {
@modal-padding: 32px;
@search-width: 372px;
@custom-width: 270px;
.modal-dialog {
margin-top: 8%;
width: calc(@modal-padding + @search-width + 2 * @modal-padding + @custom-width);
}
.modal-content {
//box-shadow: 0 3px 15px rgba(0, 0, 0, 0.2);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.10);
border: none; //1px solid #ccc;
height: 610px;
}
.modal-body {
display: flex;
flex-direction: row;
padding: 32px 32px;
.title {
color: #CCD3D5;
font-weight: 400;
font-size: 13px;
}
aside.custom {
flex: 0 auto;
padding-left: 32px;
min-width: 270px;
}
section.search {
flex: 0 auto;
min-width: 404px;
padding-right: 32px;
border-right: 1px solid #eee;
.question {
a {
color: #CCD3D5;
}
font-size: 10px;
text-align: right;
}
input {
border-radius: 20px;
font-size: 13px;
height: 38px;
padding: 8px 16px;
font-weight: 400;
color: #666;
&:focus {
box-shadow: none;
border-color: #bbb;
}
&::-webkit-input-placeholder {
color: #ddd;
font-weight: 300;
}
}
.results {
overflow: auto;
.title {
margin-top: 16px;
}
ul {
list-style: none;
color: #555;
padding: 0;
li {
display: flex;
flex-direction: row;
margin: 12px;
border-bottom: 1px solid #eee;
.info {
.name {
max-width: 278px;
img {
margin-right: 6px;
margin-left: 2px;
}
font-size: 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.stars {
color: #A7A7A7;
margin-top: 2px;
.star-count {
font-size: 10px;
display: inline-block;
position: relative;
top: -3px;
left: 1px;
height: 17px;
}
.icon {
overflow: hidden;
display: inline-block;
font-size: 15px;
height: 15px;
}
}
flex: 0 auto;
}
.action {
text-align: right;
flex: 1 auto;
}
}
}
}
}
}
}
.modal-backdrop.in {
background: rgba(227,230,230,0.95);
opacity: 1;
height: 100%;
}
@-webkit-keyframes translatedownload {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
}
}
@-webkit-keyframes translatewave {
from {
-webkit-transform: translateX(0px);
}
to {
-webkit-transform: translateX(20px);
}
}
@-webkit-keyframes translatedownload {
0% {
-webkit-transform: translateY(6px);
opacity: 0;
}
25% {
opacity: 1;
-webkit-transform: translateY(6px);
}
50% {
opacity: 1;
-webkit-transform: translateY(20px);
}
100% {
opacity: 1;
-webkit-transform: translateY(20px);
}
}

61
app/styles/header.less Normal file
View File

@ -0,0 +1,61 @@
@import "bootstrap/bootstrap.less";
.header {
min-width: 100%;
flex: 0;
min-height: 48px;
-webkit-app-region: drag;
-webkit-user-select: none;
&.no-drag {
-webkit-app-region: no-drag;
}
.buttons {
display: inline-block;
position: relative;
top: 16px;
left: 20px;
&:hover {
.button-minimize.enabled {
.at2x('minimize.png', 10px, 10px);
}
.button-close.enabled {
.at2x('close.png', 10px, 10px);
}
.button-fullscreen.enabled {
.at2x('fullscreen.png', 10px, 10px);
}
.button-fullscreenclose.enabled {
.at2x('fullscreenclose.png', 10px, 10px);
}
}
.button {
box-sizing: border-box;
display: inline-block;
background: white;
margin-right: 9px;
height: 12px;
width: 12px;
border: 1px solid #CCD3D5;
border-radius: 6px;
box-shadow: 0px 1px 1px 0px rgba(234,234,234,0.50);
-webkit-app-region: no-drag;
&.disabled {
border: 1px solid #E8EEEF;
}
&.enabled:hover {
box-shadow: 0px 1px 1px 0px rgba(195,198,201,0.50);
}
&.enabled:hover:active {
cursor: default;
-webkit-filter: brightness(92%);
}
}
}
}

View File

@ -17,24 +17,11 @@
.btn-info, .btn-info,
.btn-warning, .btn-warning,
.btn-danger { .btn-danger {
text-shadow: 0 -1px 0 rgba(0,0,0,.2);
@shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);
.box-shadow(@shadow);
// Reset the shadow
&:active,
&.active {
.box-shadow(inset 0 3px 5px rgba(0,0,0,.125));
}
.badge {
text-shadow: none;
}
} }
// Mixin for generating new styles // Mixin for generating new styles
.btn-styles(@btn-color: #555) { .btn-styles(@btn-color: #555) {
#gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));
.reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners
background-repeat: repeat-x; background-repeat: repeat-x;
border-color: darken(@btn-color, 14%); border-color: darken(@btn-color, 14%);
@ -42,33 +29,35 @@
&:hover, &:hover,
&:focus { &:focus {
background-color: darken(@btn-color, 12%); background-color: darken(@btn-color, 12%);
background-position: 0 -15px;
}
&:active,
&.active {
background-color: darken(@btn-color, 12%);
border-color: darken(@btn-color, 14%);
} }
&:disabled, &:disabled,
&[disabled] { &[disabled] {
background-color: darken(@btn-color, 12%); background-color: darken(@btn-color, 12%);
background-image: none;
} }
} }
// Common styles // Common styles
.btn { .btn {
border-radius: 25px;
box-shadow: none;
font-weight: 400;
text-shadow: none;
// Remove the gradient for the pressed/active state // Remove the gradient for the pressed/active state
&:active, &:active,
&.active { &.active {
background-image: none; background-image: none;
} }
&:focus,
&.focus {
box-shadow: none;
outline: none !important;
}
} }
// Apply the mixin to the buttons // Apply the mixin to the buttons
.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; } .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); }
.btn-info { .btn-styles(@btn-info-bg); } .btn-info { .btn-styles(@btn-info-bg); }