Merge branch 'master' into opt-in-binaries
|
@ -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 |
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
33
package.json
|
@ -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;
|
||||||
|
|
||||||
|
|