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 # Signing Identity
identity* identity*
# Integration test environment
integration
# Resources # Resources
resources/docker-* resources/docker-*
resources/boot2docker-* resources/boot2docker-*

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

@ -6,6 +6,3 @@ cache:
directories: directories:
- resources - resources
- node_modules - 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'); jest.dontMock('../src/stores/SetupStore');
var setupStore = require('./SetupStore'); var setupStore = require('../src/stores/SetupStore');
var virtualBox = require('../utils/VirtualBoxUtil'); var virtualBox = require('../src/utils/VirtualBoxUtil');
var util = require('../utils/Util'); var util = require('../src/utils/Util');
var machine = require('../utils/DockerMachineUtil'); var machine = require('../src/utils/DockerMachineUtil');
var setupUtil = require('../utils/SetupUtil'); var setupUtil = require('../src/utils/SetupUtil');
describe('SetupStore', function () { describe('SetupStore', function () {
describe('download step', function () { describe('download step', function () {

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

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

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

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

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

@ -1,6 +1,6 @@
jest.dontMock('./VirtualBoxUtil'); jest.dontMock('../src/utils/VirtualBoxUtil');
var virtualBox = require('./VirtualBoxUtil'); var virtualBox = require('../src/utils/VirtualBoxUtil');
var util = require('./Util'); var util = require('../src/utils/Util');
describe('VirtualBox', function () { describe('VirtualBox', function () {
it('returns the right command', 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? ### 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. the Docker Remote API.
### Which platforms does Kitematic support? ### 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", "name": "Kitematic",
"version": "0.5.27", "version": "0.6.0",
"author": "Kitematic", "author": "Kitematic",
"description": "Simple Docker Container management for Mac OS X.", "description": "Simple Docker Container management for Mac OS X.",
"homepage": "https://kitematic.com/", "homepage": "https://kitematic.com/",
@ -12,7 +12,8 @@
"bugs": "https://github.com/kitematic/kitematic/issues", "bugs": "https://github.com/kitematic/kitematic/issues",
"scripts": { "scripts": {
"start": "gulp", "start": "gulp",
"test": "jest", "test": "jest -c jest-unit.json",
"integration": "jest -c jest-integration.json",
"release": "gulp release", "release": "gulp release",
"release:beta": "gulp release --beta", "release:beta": "gulp release --beta",
"lint": "jsxhint src", "lint": "jsxhint src",
@ -24,30 +25,6 @@
"url": "http://www.apache.org/licenses/LICENSE-2.0.html" "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-version": "1.6.2",
"docker-machine-version": "0.2.0", "docker-machine-version": "0.2.0",
"electron-version": "0.26.0", "electron-version": "0.26.0",
@ -66,8 +43,8 @@
"classnames": "^1.2.0", "classnames": "^1.2.0",
"coveralls": "^2.11.2", "coveralls": "^2.11.2",
"deep-extend": "^0.4.0", "deep-extend": "^0.4.0",
"dockerode": "^2.1.4",
"exec": "0.2.0", "exec": "0.2.0",
"dockerode": "^2.1.4",
"install": "^0.1.8", "install": "^0.1.8",
"jquery": "^2.1.3", "jquery": "^2.1.3",
"mixpanel": "0.2.0", "mixpanel": "0.2.0",
@ -102,7 +79,7 @@
"gulp-shell": "^0.4.1", "gulp-shell": "^0.4.1",
"gulp-sourcemaps": "^1.5.2", "gulp-sourcemaps": "^1.5.2",
"gulp-util": "^3.0.4", "gulp-util": "^3.0.4",
"jest-cli": "kitematic/jest", "jest-cli": "^0.4.5",
"jsxhint": "^0.14.0", "jsxhint": "^0.14.0",
"minimist": "^1.1.1", "minimist": "^1.1.1",
"react-tools": "^0.13.1", "react-tools": "^0.13.1",

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

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

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

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

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

@ -3,7 +3,8 @@ import alt from '../alt';
class TagServerActions { class TagServerActions {
constructor () { constructor () {
this.generateActions( 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; let loading = this.props.loading ? <div className="spinner la-ball-clip-rotate la-dark"><div></div></div> : null;
return ( return (
<form className="form-connect"> <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> <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> <p className="error-message">{this.state.errors.password}</p>
<a className="link" onClick={this.handleClickForgotPassword}>Forgot your password?</a> <a className="link" onClick={this.handleClickForgotPassword}>Forgot your password?</a>
<p className="error-message">{this.state.errors.detail}</p> <p className="error-message">{this.state.errors.detail}</p>

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

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

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

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

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

@ -126,7 +126,7 @@ module.exports = React.createClass({
<div className="no-results"> <div className="no-results">
<h2>Please verify your Docker Hub account email address</h2> <h2>Please verify your Docker Hub account email address</h2>
<div className="verify"> <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> </div>
<RetinaImage src="inspection.png" checkIfRetinaImgExists={false}/> <RetinaImage src="inspection.png" checkIfRetinaImgExists={false}/>
</div> </div>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -193,6 +193,16 @@ input[type="text"] {
} }
.btn-positive { .btn-positive {
.btn-styles(@brand-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-default { .btn-styles(@btn-default-bg); }
.btn-primary { .btn-styles(@btn-primary-bg); } .btn-primary { .btn-styles(@btn-primary-bg); }

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

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

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

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