diff --git a/.gitignore b/.gitignore index 5f98bcf3..3b0964fe 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ npm-debug.log # Signing Identity identity* +# Integration test environment +integration + # Resources resources/docker-* resources/boot2docker-* diff --git a/.travis.yml b/.travis.yml index a9888c78..78c160f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/__integration__/HubUtil-integration.js b/__integration__/HubUtil-integration.js new file mode 100644 index 00000000..cdde6a0c --- /dev/null +++ b/__integration__/HubUtil-integration.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(); + }); + }); + }); + }); +}); diff --git a/__integration__/RegHubUtil-integration.js b/__integration__/RegHubUtil-integration.js new file mode 100644 index 00000000..ee49ddd9 --- /dev/null +++ b/__integration__/RegHubUtil-integration.js @@ -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(); + }); + }); + }); + }); + }); +}); diff --git a/src/stores/SetupStore-test.js b/__tests__/SetupStore-test.js similarity index 89% rename from src/stores/SetupStore-test.js rename to __tests__/SetupStore-test.js index 66e0a251..ef9daa1d 100644 --- a/src/stores/SetupStore-test.js +++ b/__tests__/SetupStore-test.js @@ -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 () { diff --git a/src/utils/URLUtil-test.js b/__tests__/URLUtil-test.js similarity index 95% rename from src/utils/URLUtil-test.js rename to __tests__/URLUtil-test.js index cfa13777..2c121252 100644 --- a/src/utils/URLUtil-test.js +++ b/__tests__/URLUtil-test.js @@ -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(() => { diff --git a/src/utils/Util-test.js b/__tests__/Util-test.js similarity index 98% rename from src/utils/Util-test.js rename to __tests__/Util-test.js index 471b1088..985d05bd 100644 --- a/src/utils/Util-test.js +++ b/__tests__/Util-test.js @@ -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 () { diff --git a/src/utils/VirtualBoxUtil-test.js b/__tests__/VirtualBoxUtil-test.js similarity index 77% rename from src/utils/VirtualBoxUtil-test.js rename to __tests__/VirtualBoxUtil-test.js index 7af88edd..3bf9a442 100644 --- a/src/utils/VirtualBoxUtil-test.js +++ b/__tests__/VirtualBoxUtil-test.js @@ -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 () { diff --git a/docs/faq.md b/docs/faq.md index ada4fa5c..0fef59e2 100755 --- a/docs/faq.md +++ b/docs/faq.md @@ -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? diff --git a/images/feedback.png b/images/feedback.png index f4a0d825..ce9dc049 100644 Binary files a/images/feedback.png and b/images/feedback.png differ diff --git a/images/feedback@2x.png b/images/feedback@2x.png index bfe5e7f9..9b81ce6e 100644 Binary files a/images/feedback@2x.png and b/images/feedback@2x.png differ diff --git a/images/preferences.png b/images/preferences.png index c533e97d..e0bdc2f3 100644 Binary files a/images/preferences.png and b/images/preferences.png differ diff --git a/images/preferences@2x.png b/images/preferences@2x.png index 3aba6c04..2b8eda4f 100644 Binary files a/images/preferences@2x.png and b/images/preferences@2x.png differ diff --git a/images/user.png b/images/user.png index 82cae572..8ecbd702 100644 Binary files a/images/user.png and b/images/user.png differ diff --git a/images/user@2x.png b/images/user@2x.png index 0d0991ce..3d9b9cd2 100644 Binary files a/images/user@2x.png and b/images/user@2x.png differ diff --git a/images/whaleicon.png b/images/whaleicon.png index 410aef41..21a1e290 100644 Binary files a/images/whaleicon.png and b/images/whaleicon.png differ diff --git a/images/whaleicon@2x.png b/images/whaleicon@2x.png index 941ed32f..de6ff361 100644 Binary files a/images/whaleicon@2x.png and b/images/whaleicon@2x.png differ diff --git a/jest-integration.json b/jest-integration.json new file mode 100644 index 00000000..1405f904 --- /dev/null +++ b/jest-integration.json @@ -0,0 +1,10 @@ +{ + "testDirectoryName": "__integration__", + "scriptPreprocessor": "/util/preprocessor.js", + "setupEnvScriptFile": "/util/testenv.js", + "setupTestFrameworkScriptFile": "/util/prepare.js", + "unmockedModulePathPatterns": [ + "babel", + "/node_modules/source-map-support" + ] +} diff --git a/jest-unit.json b/jest-unit.json new file mode 100644 index 00000000..0981c13e --- /dev/null +++ b/jest-unit.json @@ -0,0 +1,18 @@ +{ + "scriptPreprocessor": "/util/preprocessor.js", + "setupEnvScriptFile": "/util/testenv.js", + "setupTestFrameworkScriptFile": "/util/prepare.js", + "unmockedModulePathPatterns": [ + "alt", + "stream", + "tty", + "net", + "crypto", + "babel", + "/node_modules/.*JSONStream", + "/node_modules/object-assign", + "/node_modules/underscore", + "/node_modules/bluebird", + "/node_modules/source-map-support" + ] +} diff --git a/package.json b/package.json index 9695288c..25ce4dd1 100644 --- a/package.json +++ b/package.json @@ -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": "/util/preprocessor.js", - "setupEnvScriptFile": "/util/testenv.js", - "setupTestFrameworkScriptFile": "/util/prepare.js", - "collectCoverage": true, - "testDirectoryName": "src", - "testPathIgnorePatterns": [ - "/node_modules/", - "^((?!-test).)*$" - ], - "unmockedModulePathPatterns": [ - "alt", - "stream", - "tty", - "net", - "crypto", - "babel", - "/node_modules/.*JSONStream", - "/node_modules/object-assign", - "/node_modules/underscore", - "/node_modules/bluebird", - "/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", diff --git a/src/actions/ContainerActions.js b/src/actions/ContainerActions.js index e5fd4e1d..d3e5996d 100644 --- a/src/actions/ContainerActions.js +++ b/src/actions/ContainerActions.js @@ -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 () { diff --git a/src/actions/RepositoryServerActions.js b/src/actions/RepositoryServerActions.js index b49e4d60..9b4740ff 100644 --- a/src/actions/RepositoryServerActions.js +++ b/src/actions/RepositoryServerActions.js @@ -6,7 +6,8 @@ class RepositoryServerActions { 'reposLoading', 'resultsUpdated', 'recommendedUpdated', - 'reposUpdated' + 'reposUpdated', + 'error' ); } } diff --git a/src/actions/TagServerActions.js b/src/actions/TagServerActions.js index 4149b81c..d8356b6c 100644 --- a/src/actions/TagServerActions.js +++ b/src/actions/TagServerActions.js @@ -3,7 +3,8 @@ import alt from '../alt'; class TagServerActions { constructor () { this.generateActions( - 'tagsUpdated' + 'tagsUpdated', + 'error' ); } } diff --git a/src/components/AccountLogin.react.js b/src/components/AccountLogin.react.js index d189f1c7..427c2725 100644 --- a/src/components/AccountLogin.react.js +++ b/src/components/AccountLogin.react.js @@ -67,9 +67,9 @@ module.exports = React.createClass({ let loading = this.props.loading ?
: null; return (
- +

{this.state.errors.username}

- +

{this.state.errors.password}

Forgot your password?

{this.state.errors.detail}

diff --git a/src/components/ContainerDetailsSubheader.react.js b/src/components/ContainerDetailsSubheader.react.js index 9b4cda57..f5ee477c 100644 --- a/src/components/ContainerDetailsSubheader.react.js +++ b/src/components/ContainerDetailsSubheader.react.js @@ -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({
Restart - {{startStopToggle}} + {startStopToggle}
Terminal diff --git a/src/components/ImageCard.react.js b/src/components/ImageCard.react.js index 5a94d488..86efd89c 100644 --- a/src/components/ImageCard.react.js +++ b/src/components/ImageCard.react.js @@ -144,6 +144,7 @@ var ImageCard = React.createClass({ return (
+

Please select an image tag.

{tags}
@@ -169,7 +170,7 @@ var ImageCard = React.createClass({ {this.state.chosenTag}
diff --git a/src/components/NewContainerSearch.react.js b/src/components/NewContainerSearch.react.js index 95988559..8b18ce97 100644 --- a/src/components/NewContainerSearch.react.js +++ b/src/components/NewContainerSearch.react.js @@ -126,7 +126,7 @@ module.exports = React.createClass({

Please verify your Docker Hub account email address

- {spinner} + {spinner}
diff --git a/src/stores/TagStore.js b/src/stores/TagStore.js index 9293bb16..13a2b980 100644 --- a/src/stores/TagStore.js +++ b/src/stores/TagStore.js @@ -38,6 +38,11 @@ class TagStore { this.tags = {}; this.emitChange(); } + + error ({repo}) { + this.loading[repo] = false; + this.emitChange(); + } } export default alt.createStore(TagStore); diff --git a/src/utils/DockerUtil.js b/src/utils/DockerUtil.js index 9a7e14c0..0ec52f08 100644 --- a/src/utils/DockerUtil.js +++ b/src/utils/DockerUtil.js @@ -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; } diff --git a/src/utils/HubUtil.js b/src/utils/HubUtil.js index a4db7131..02ce497a 100644 --- a/src/utils/HubUtil.js +++ b/src/utils/HubUtil.js @@ -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, diff --git a/src/utils/RegHubUtil.js b/src/utils/RegHubUtil.js index 3975ce96..25e9b3e6 100644 --- a/src/utils/RegHubUtil.js +++ b/src/utils/RegHubUtil.js @@ -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); } }); }); } diff --git a/styles/container-home.less b/styles/container-home.less index 8a11e5d8..e13bc062 100644 --- a/styles/container-home.less +++ b/styles/container-home.less @@ -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; diff --git a/styles/container-settings.less b/styles/container-settings.less index 46e692a5..13510553 100644 --- a/styles/container-settings.less +++ b/styles/container-settings.less @@ -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; diff --git a/styles/left-panel.less b/styles/left-panel.less index 160bce82..4fe83129 100644 --- a/styles/left-panel.less +++ b/styles/left-panel.less @@ -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; } diff --git a/styles/new-container.less b/styles/new-container.less index f6dfb928..141228b8 100644 --- a/styles/new-container.less +++ b/styles/new-container.less @@ -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; diff --git a/styles/right-panel.less b/styles/right-panel.less index d8dfba88..3ceb91d2 100644 --- a/styles/right-panel.less +++ b/styles/right-panel.less @@ -119,6 +119,10 @@ background-color: transparent; } } + &:hover { + border-radius: 40px; + background-color: @gray-lightest; + } } .details-progress { diff --git a/styles/theme.less b/styles/theme.less index d20daccc..e5bda861 100644 --- a/styles/theme.less +++ b/styles/theme.less @@ -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); } diff --git a/styles/variables.less b/styles/variables.less index 64cbc2b8..83e17a8c 100644 --- a/styles/variables.less +++ b/styles/variables.less @@ -15,6 +15,7 @@ @gray-darkest: #233137; @gray-darker: #556473; @gray-normal: #7A8491; +@gray-light: #9AA7BB; @gray-lighter: #C4CDDA; @gray-lightest: #E1E8EF; diff --git a/util/testenv.js b/util/testenv.js index 55db09ad..d2a95c4a 100644 --- a/util/testenv.js +++ b/util/testenv.js @@ -1,6 +1,6 @@ var mock = (function() { var store = {}; - return { + return { getItem: function(key) { return store[key]; },