Merge branch 'master' into opt-in-binaries

This commit is contained in:
Jeffrey Morgan 2015-05-27 14:53:11 -07:00
Родитель 1e5a210ab5 92242b7d91
Коммит 43b1b9249a
39 изменённых файлов: 253 добавлений и 89 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -9,6 +9,9 @@ npm-debug.log
# Signing Identity
identity*
# Integration test environment
integration
# Resources
resources/docker-*
resources/boot2docker-*

Просмотреть файл

@ -6,6 +6,3 @@ cache:
directories:
- resources
- node_modules
after_success:
- which ./node_modules/coveralls/bin/coveralls.js && cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js

Просмотреть файл

@ -0,0 +1,32 @@
jest.autoMockOff();
let hubUtil = require('../src/utils/HubUtil');
let Promise = require('bluebird');
jasmine.getEnv().DEFAULT_TIMEOUT_INTERVAL = 60000;
describe('HubUtil Integration Tests', () => {
describe('auth', () => {
pit('successfully authenticates', () => {
return new Promise((resolve) => {
hubUtil.auth(process.env.INTEGRATION_USER, process.env.INTEGRATION_PASSWORD, (error, response, body) => {
expect(response.statusCode).toBe(200);
expect(error).toBe(null);
let data = JSON.parse(body);
expect(data.token).toBeTruthy();
resolve();
});
});
});
pit('provides a 401 if credentials are incorrect', () => {
return new Promise((resolve) => {
hubUtil.auth(process.env.INTEGRATION_USER, 'incorrectpassword', (error, response) => {
expect(response.statusCode).toBe(401);
resolve();
});
});
});
});
});

Просмотреть файл

@ -0,0 +1,61 @@
jest.autoMockOff();
// One minute timeout for integration tests
jasmine.getEnv().DEFAULT_TIMEOUT_INTERVAL = 60000;
let _ = require('underscore');
let regHubUtil = require('../src/utils/RegHubUtil');
let hubUtil = require('../src/utils/HubUtil');
let Promise = require('bluebird');
describe('RegHubUtil Integration Tests', () => {
describe('with login', () => {
pit('lists private repos', () => {
return new Promise((resolve) => {
hubUtil.login(process.env.INTEGRATION_USER, process.env.INTEGRATION_PASSWORD, () => {
regHubUtil.repos((error, repos) => {
expect(_.findWhere(repos, {name: 'test_private', is_private: true})).toBeTruthy();
resolve();
});
});
});
});
pit('lists tags for a private repo', () => {
return new Promise((resolve) => {
hubUtil.login(process.env.INTEGRATION_USER, process.env.INTEGRATION_PASSWORD, () => {
regHubUtil.tags(`${process.env.INTEGRATION_USER}/test_private`, (error, tags) => {
expect(error).toBeFalsy();
expect(tags).toEqual(['latest']);
resolve();
});
});
});
});
});
describe('public repos', () => {
pit('lists repos', () => {
return new Promise((resolve) => {
hubUtil.login(process.env.INTEGRATION_USER, process.env.INTEGRATION_PASSWORD, () => {
regHubUtil.repos((error, repos) => {
expect(_.findWhere(repos, {name: 'test'})).toBeTruthy();
resolve();
});
});
});
});
pit('lists tags for a repo', () => {
return new Promise((resolve) => {
hubUtil.login(process.env.INTEGRATION_USER, process.env.INTEGRATION_PASSWORD, () => {
regHubUtil.tags(`${process.env.INTEGRATION_USER}/test`, (error, tags) => {
expect(error).toBeFalsy();
expect(tags).toEqual(['latest']);
resolve();
});
});
});
});
});
});

Просмотреть файл

@ -1,9 +1,9 @@
jest.dontMock('./SetupStore');
var setupStore = require('./SetupStore');
var virtualBox = require('../utils/VirtualBoxUtil');
var util = require('../utils/Util');
var machine = require('../utils/DockerMachineUtil');
var setupUtil = require('../utils/SetupUtil');
jest.dontMock('../src/stores/SetupStore');
var setupStore = require('../src/stores/SetupStore');
var virtualBox = require('../src/utils/VirtualBoxUtil');
var util = require('../src/utils/Util');
var machine = require('../src/utils/DockerMachineUtil');
var setupUtil = require('../src/utils/SetupUtil');
describe('SetupStore', function () {
describe('download step', function () {

Просмотреть файл

@ -1,7 +1,7 @@
jest.dontMock('./URLUtil');
jest.dontMock('../src/utils/URLUtil');
jest.dontMock('parseUri');
var urlUtil = require('./URLUtil');
var util = require('./Util');
var urlUtil = require('../src/utils/URLUtil');
var util = require('../src/utils/Util');
describe('URLUtil', function () {
beforeEach(() => {

Просмотреть файл

@ -1,5 +1,5 @@
jest.dontMock('./Util');
var util = require('./Util');
jest.dontMock('../src/utils/Util');
var util = require('../src/utils/Util');
describe('Util', function () {
describe('when removing sensitive data', function () {

Просмотреть файл

@ -1,6 +1,6 @@
jest.dontMock('./VirtualBoxUtil');
var virtualBox = require('./VirtualBoxUtil');
var util = require('./Util');
jest.dontMock('../src/utils/VirtualBoxUtil');
var virtualBox = require('../src/utils/VirtualBoxUtil');
var util = require('../src/utils/Util');
describe('VirtualBox', function () {
it('returns the right command', function () {

Просмотреть файл

@ -19,7 +19,7 @@ best way to start contributing to Kitematic is to review our doc on
### How does Kitematic work with Docker?
Kitematic connects directly do a running instance of Docker and controls it via
Kitematic connects directly to a running instance of Docker and controls it via
the Docker Remote API.
### Which platforms does Kitematic support?

Двоичные данные
images/feedback.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 671 B

После

Ширина:  |  Высота:  |  Размер: 899 B

Двоичные данные
images/feedback@2x.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.3 KiB

После

Ширина:  |  Высота:  |  Размер: 1.9 KiB

Двоичные данные
images/preferences.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 624 B

После

Ширина:  |  Высота:  |  Размер: 1.1 KiB

Двоичные данные
images/preferences@2x.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.2 KiB

После

Ширина:  |  Высота:  |  Размер: 2.1 KiB

Двоичные данные
images/user.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 931 B

После

Ширина:  |  Высота:  |  Размер: 841 B

Двоичные данные
images/user@2x.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.9 KiB

После

Ширина:  |  Высота:  |  Размер: 1.8 KiB

Двоичные данные
images/whaleicon.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 711 B

После

Ширина:  |  Высота:  |  Размер: 1.0 KiB

Двоичные данные
images/whaleicon@2x.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.2 KiB

После

Ширина:  |  Высота:  |  Размер: 2.1 KiB

10
jest-integration.json Normal file
Просмотреть файл

@ -0,0 +1,10 @@
{
"testDirectoryName": "__integration__",
"scriptPreprocessor": "<rootDir>/util/preprocessor.js",
"setupEnvScriptFile": "<rootDir>/util/testenv.js",
"setupTestFrameworkScriptFile": "<rootDir>/util/prepare.js",
"unmockedModulePathPatterns": [
"babel",
"<rootDir>/node_modules/source-map-support"
]
}

18
jest-unit.json Normal file
Просмотреть файл

@ -0,0 +1,18 @@
{
"scriptPreprocessor": "<rootDir>/util/preprocessor.js",
"setupEnvScriptFile": "<rootDir>/util/testenv.js",
"setupTestFrameworkScriptFile": "<rootDir>/util/prepare.js",
"unmockedModulePathPatterns": [
"alt",
"stream",
"tty",
"net",
"crypto",
"babel",
"<rootDir>/node_modules/.*JSONStream",
"<rootDir>/node_modules/object-assign",
"<rootDir>/node_modules/underscore",
"<rootDir>/node_modules/bluebird",
"<rootDir>/node_modules/source-map-support"
]
}

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "Kitematic",
"version": "0.5.27",
"version": "0.6.0",
"author": "Kitematic",
"description": "Simple Docker Container management for Mac OS X.",
"homepage": "https://kitematic.com/",
@ -12,7 +12,8 @@
"bugs": "https://github.com/kitematic/kitematic/issues",
"scripts": {
"start": "gulp",
"test": "jest",
"test": "jest -c jest-unit.json",
"integration": "jest -c jest-integration.json",
"release": "gulp release",
"release:beta": "gulp release --beta",
"lint": "jsxhint src",
@ -24,30 +25,6 @@
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
],
"jest": {
"scriptPreprocessor": "<rootDir>/util/preprocessor.js",
"setupEnvScriptFile": "<rootDir>/util/testenv.js",
"setupTestFrameworkScriptFile": "<rootDir>/util/prepare.js",
"collectCoverage": true,
"testDirectoryName": "src",
"testPathIgnorePatterns": [
"/node_modules/",
"^((?!-test).)*$"
],
"unmockedModulePathPatterns": [
"alt",
"stream",
"tty",
"net",
"crypto",
"babel",
"<rootDir>/node_modules/.*JSONStream",
"<rootDir>/node_modules/object-assign",
"<rootDir>/node_modules/underscore",
"<rootDir>/node_modules/bluebird",
"<rootDir>/node_modules/source-map-support"
]
},
"docker-version": "1.6.2",
"docker-machine-version": "0.2.0",
"electron-version": "0.26.0",
@ -66,8 +43,8 @@
"classnames": "^1.2.0",
"coveralls": "^2.11.2",
"deep-extend": "^0.4.0",
"dockerode": "^2.1.4",
"exec": "0.2.0",
"dockerode": "^2.1.4",
"install": "^0.1.8",
"jquery": "^2.1.3",
"mixpanel": "0.2.0",
@ -102,7 +79,7 @@
"gulp-shell": "^0.4.1",
"gulp-sourcemaps": "^1.5.2",
"gulp-util": "^3.0.4",
"jest-cli": "kitematic/jest",
"jest-cli": "^0.4.5",
"jsxhint": "^0.14.0",
"minimist": "^1.1.1",
"react-tools": "^0.13.1",

Просмотреть файл

@ -2,10 +2,6 @@ import alt from '../alt';
import dockerUtil from '../utils/DockerUtil';
class ContainerActions {
start (name) {
this.dispatch({name});
dockerUtil.start(name);
}
destroy (name) {
this.dispatch({name});
@ -17,14 +13,24 @@ class ContainerActions {
dockerUtil.rename(name, newName);
}
start (name) {
this.dispatch({name});
dockerUtil.start(name);
}
stop (name) {
this.dispatch({name});
dockerUtil.stop(name);
}
update (name, container) {
this.dispatch({name, container});
dockerUtil.updateContainer(name, container);
restart (name) {
this.dispatch({name});
dockerUtil.restart(name);
}
update (name, containerOpts) {
this.dispatch({name, containerOpts});
dockerUtil.updateContainer(name, containerOpts);
}
clearPending () {

Просмотреть файл

@ -6,7 +6,8 @@ class RepositoryServerActions {
'reposLoading',
'resultsUpdated',
'recommendedUpdated',
'reposUpdated'
'reposUpdated',
'error'
);
}
}

Просмотреть файл

@ -3,7 +3,8 @@ import alt from '../alt';
class TagServerActions {
constructor () {
this.generateActions(
'tagsUpdated'
'tagsUpdated',
'error'
);
}
}

Просмотреть файл

@ -67,9 +67,9 @@ module.exports = React.createClass({
let loading = this.props.loading ? <div className="spinner la-ball-clip-rotate la-dark"><div></div></div> : null;
return (
<form className="form-connect">
<input ref="usernameInput"maxLength="30" name="username" placeholder="username" type="text" disabled={this.props.loading} valueLink={this.linkState('username')} onBlur={this.handleBlur}/>
<input ref="usernameInput"maxLength="30" name="username" placeholder="Username" type="text" disabled={this.props.loading} valueLink={this.linkState('username')} onBlur={this.handleBlur}/>
<p className="error-message">{this.state.errors.username}</p>
<input ref="passwordInput" name="password" placeholder="password" type="password" disabled={this.props.loading} valueLink={this.linkState('password')} onBlur={this.handleBlur}/>
<input ref="passwordInput" name="password" placeholder="Password" type="password" disabled={this.props.loading} valueLink={this.linkState('password')} onBlur={this.handleBlur}/>
<p className="error-message">{this.state.errors.password}</p>
<a className="link" onClick={this.handleClickForgotPassword}>Forgot your password?</a>
<p className="error-message">{this.state.errors.detail}</p>

Просмотреть файл

@ -83,7 +83,7 @@ var ContainerDetailsSubheader = React.createClass({
handleRestart: function () {
if (!this.disableRestart()) {
metrics.track('Restarted Container');
dockerUtil.restart(this.props.container.Name);
containerActions.restart(this.props.container.Name);
}
},
handleStop: function () {
@ -223,7 +223,7 @@ var ContainerDetailsSubheader = React.createClass({
<div className="action-icon" onClick={this.handleRestart}><RetinaImage src="button-restart.png"/></div>
<span className="btn-label restart">Restart</span>
</div>
{{startStopToggle}}
{startStopToggle}
<div className={terminalActionClass} onMouseEnter={this.handleItemMouseEnterTerminal} onMouseLeave={this.handleItemMouseLeaveTerminal}>
<div className="action-icon" onClick={this.handleTerminal}><RetinaImage src="button-terminal.png"/></div>
<span className="btn-label terminal">Terminal</span>

Просмотреть файл

@ -144,6 +144,7 @@ var ImageCard = React.createClass({
return (
<div className="image-item">
<div className="tag-overlay" onClick={self.handleCloseTagOverlay}>
<p>Please select an image tag.</p>
{tags}
</div>
<div className="logo" style={logoStyle}>
@ -169,7 +170,7 @@ var ImageCard = React.createClass({
<span className="text" onClick={self.handleTagOverlayClick.bind(self, this.props.image.name)} data-name={this.props.image.name}>{this.state.chosenTag}</span>
</div>
<div className="action">
<a className="btn btn-action" onClick={self.handleClick}>Create</a>
<a className="btn btn-action btn-positive" onClick={self.handleClick}>Create</a>
</div>
</div>
</div>

Просмотреть файл

@ -126,7 +126,7 @@ module.exports = React.createClass({
<div className="no-results">
<h2>Please verify your Docker Hub account email address</h2>
<div className="verify">
<button className="btn btn-primary btn-lg" onClick={this.handleCheckVerification}>{'I\'ve Verified my Email Address'}</button> {spinner}
<button className="btn btn-primary btn-lg" onClick={this.handleCheckVerification}>{'I\'ve Verified My Email Address'}</button> {spinner}
</div>
<RetinaImage src="inspection.png" checkIfRetinaImgExists={false}/>
</div>

Просмотреть файл

@ -38,6 +38,11 @@ class TagStore {
this.tags = {};
this.emitChange();
}
error ({repo}) {
this.loading[repo] = false;
this.emitChange();
}
}
export default alt.createStore(TagStore);

Просмотреть файл

@ -73,6 +73,8 @@ export default {
if (containerData.NetworkSettings && containerData.NetworkSettings.Ports) {
startopts.PortBindings = containerData.NetworkSettings.Ports;
} else if (containerData.HostConfig && containerData.HostConfig.PortBindings) {
startopts.PortBindings = containerData.HostConfig.PortBindings;
} else {
startopts.PublishAllPorts = true;
}

Просмотреть файл

@ -2,6 +2,8 @@ var _ = require('underscore');
var request = require('request');
var accountServerActions = require('../actions/AccountServerActions');
let HUB2_ENDPOINT = process.env.HUB2_ENDPOINT || 'https://hub.docker.com/v2';
module.exports = {
init: function () {
accountServerActions.prompted({prompted: localStorage.getItem('auth.prompted')});
@ -95,10 +97,11 @@ module.exports = {
localStorage.removeItem('auth.config');
},
login: function (username, password) {
login: function (username, password, callback) {
this.auth(username, password, (error, response, body) => {
if (error) {
accountServerActions.errors({errors: {detail: error.message}});
callback(error);
return;
}
@ -112,9 +115,11 @@ module.exports = {
localStorage.setItem('auth.config', new Buffer(username + ':' + password).toString('base64'));
accountServerActions.loggedin({username, verified: true});
accountServerActions.prompted({prompted: true});
if (callback) { callback(); }
require('./RegHubUtil').repos();
} else {
accountServerActions.errors({errors: {detail: 'Did not receive login token.'}});
if (callback) { callback(new Error('Did not receive login token.')); }
}
} else if (response.statusCode === 401) {
if (data && data.detail && data.detail.indexOf('Account not active yet') !== -1) {
@ -123,15 +128,17 @@ module.exports = {
localStorage.setItem('auth.username', username);
localStorage.setItem('auth.verified', false);
localStorage.setItem('auth.config', new Buffer(username + ':' + password).toString('base64'));
if (callback) { callback(); }
} else {
accountServerActions.errors({errors: data});
if (callback) { callback(new Error(data.detail)); }
}
}
});
},
auth: function (username, password, callback) {
request.post('https://hub.docker.com/v2/users/login/', {form: {username, password}}, (error, response, body) => {
request.post(`${HUB2_ENDPOINT}/users/login/`, {form: {username, password}}, (error, response, body) => {
callback(error, response, body);
});
},
@ -153,7 +160,7 @@ module.exports = {
// Signs up and places a token under ~/.dockercfg and saves a jwt to localstore
signup: function (username, password, email, subscribe) {
request.post('https://hub.docker.com/v2/users/signup/', {
request.post('${HUB2_ENDPOINT}/users/signup/', {
form: {
username,
password,

Просмотреть файл

@ -5,7 +5,9 @@ var util = require('../utils/Util');
var hubUtil = require('../utils/HubUtil');
var repositoryServerActions = require('../actions/RepositoryServerActions');
var tagServerActions = require('../actions/TagServerActions');
var Promise = require('bluebird');
let REGHUB2_ENDPOINT = process.env.REGHUB2_ENDPOINT || 'https://registry.hub.docker.com/v2';
let searchReq = null;
module.exports = {
@ -38,7 +40,7 @@ module.exports = {
qs: {q: query, page}
}, (error, response, body) => {
if (error) {
repositoryServerActions.searchError({error});
repositoryServerActions.error({error});
}
let data = JSON.parse(body);
@ -66,7 +68,7 @@ module.exports = {
}
request.get({
url: `https://registry.hub.docker.com/v2/repositories/${name}`,
url: `${REGHUB2_ENDPOINT}/repositories/${name}`,
}, (error, response, body) => {
if (error) {
repositoryServerActions.error({error});
@ -86,28 +88,38 @@ module.exports = {
});
},
tags: function (repo) {
tags: function (repo, callback) {
hubUtil.request({
url: `https://registry.hub.docker.com/v2/repositories/${repo}/tags`
url: `${REGHUB2_ENDPOINT}/repositories/${repo}/tags`
}, (error, response, body) => {
if (response.statusCode === 200) {
let data = JSON.parse(body);
tagServerActions.tagsUpdated({repo, tags: data.tags});
} else if (response.statusCude === 401) {
return;
if (callback) { callback(null, data.tags); }
} else if (error || response.statusCode === 401) {
repositoryServerActions.error({repo});
if (callback) { callback(new Error('Failed to fetch repos')); }
}
});
},
// Returns the base64 encoded index token or null if no token exists
repos: function () {
repos: function (callback) {
repositoryServerActions.reposLoading({repos: []});
hubUtil.request({
url: 'https://registry.hub.docker.com/v2/namespaces/',
url: `${REGHUB2_ENDPOINT}/namespaces/`,
}, (error, response, body) => {
if (error) {
repositoryServerActions.reposError({error});
repositoryServerActions.error({error});
if (callback) { callback(error); }
return;
}
if (response.statusCode !== 200) {
let generalError = new Error('Failed to fetch repos');
repositoryServerActions.error({error: generalError});
if (callback) { callback({error: generalError}); }
return;
}
@ -115,10 +127,11 @@ module.exports = {
let namespaces = data.namespaces;
async.map(namespaces, (namespace, cb) => {
hubUtil.request({
url: `https://registry.hub.docker.com/v2/repositories/${namespace}`
url: `${REGHUB2_ENDPOINT}/repositories/${namespace}`
}, (error, response, body) => {
if (error) {
repositoryServerActions.reposError({error});
repositoryServerActions.error({error});
if (callback) { callback(error); }
return;
}
@ -126,6 +139,12 @@ module.exports = {
cb(null, data.results);
});
}, (error, lists) => {
if (error) {
repositoryServerActions.error({error});
if (callback) { callback(error); }
return;
}
let repos = [];
for (let list of lists) {
repos = repos.concat(list);
@ -136,6 +155,7 @@ module.exports = {
});
repositoryServerActions.reposUpdated({repos});
if (callback) { callback(null, repos); }
});
});
}

Просмотреть файл

@ -30,7 +30,7 @@
.widget-style();
background-color: white;
width: 100%;
height: 100%;
height: 95%;
p {
font-size: 13px;
color: @gray-normal;
@ -55,7 +55,7 @@
flex: 1 auto;
display: flex;
font-size: 10px;
color: @gray-lightest;
color: @gray-light;
.label-left {
flex: 0 auto;
min-width: 60px;

Просмотреть файл

@ -72,7 +72,7 @@
.env-vars-labels {
width: 100%;
font-size: 12px;
color: @gray-lightest;
color: @gray-light;
margin-left: 5px;
margin-bottom: 5px;
margin-top: 20px;
@ -116,7 +116,7 @@
flex: 1 auto;
display: flex;
font-size: 12px;
color: @gray-lightest;
color: @gray-light;
.label-left {
flex: 0 auto;
min-width: 85px;
@ -164,7 +164,7 @@
flex: 1 auto;
display: flex;
font-size: 12px;
color: @gray-lightest;
color: @gray-light;
.label-left {
flex: 0 auto;
margin-right: 30px;

Просмотреть файл

@ -153,7 +153,7 @@
color: @gray-darkest;
}
.image {
color: @gray-lighter;
color: @gray-light;
font-size: 10px;
font-weight: 400;
text-overflow: ellipsis;
@ -270,7 +270,7 @@
&:active {
img, .text {
-webkit-filter: brightness(0.8);
-webkit-filter: brightness(0.7);
}
}
}
@ -279,12 +279,9 @@
flex: 1 auto;
border-right: 1px solid #F0F4F8;
img {
margin: 0 10px 0 0;
height: 21px;
width: 25px;
margin-right: 10px;
}
}
.btn-feedback {
border-right: 1px solid #F0F4F8;
}

Просмотреть файл

@ -50,13 +50,19 @@
flex-direction: row;
justify-content: flex-end;
font-size: 13px;
margin: 0 10px;
margin-bottom: 10px;
margin: 0 20px;
.results-filter {
text-align: center;
margin: 0 10px;
min-width: 40px;
&.tab {
&:hover {
border-radius: 40px;
background-color: @gray-lightest;
}
}
}
.results-filter-title {
@ -188,13 +194,18 @@
font-size: 13px;
display: none;
padding: 10px;
p {
color: white;
padding-bottom: 7px;
border-bottom: 1px solid rgba(255,255,255,0.2);
}
.tag-list {
display: flex;
flex-direction: row;
align-items: flex-start;
align-content: flex-start;
flex-flow: row wrap;
height: 140px;
height: 100px;
overflow: auto;
.tag {
display: inline-block;

Просмотреть файл

@ -119,6 +119,10 @@
background-color: transparent;
}
}
&:hover {
border-radius: 40px;
background-color: @gray-lightest;
}
}
.details-progress {

Просмотреть файл

@ -193,6 +193,16 @@ input[type="text"] {
}
.btn-positive {
.btn-styles(@brand-positive);
&:hover,
&:focus {
border-color: darken(@brand-positive, 7%);
color: darken(@brand-positive, 7%);
}
&:active {
background-color: lighten(@brand-positive, 53%);
border-color: darken(@brand-positive, 7%);
color: darken(@brand-positive, 7%);
}
}
.btn-default { .btn-styles(@btn-default-bg); }
.btn-primary { .btn-styles(@btn-primary-bg); }

Просмотреть файл

@ -15,6 +15,7 @@
@gray-darkest: #233137;
@gray-darker: #556473;
@gray-normal: #7A8491;
@gray-light: #9AA7BB;
@gray-lighter: #C4CDDA;
@gray-lightest: #E1E8EF;

Просмотреть файл

@ -1,6 +1,6 @@
var mock = (function() {
var store = {};
return {
return {
getItem: function(key) {
return store[key];
},