mirror of https://github.com/docker/docs.git
Merged with master
This commit is contained in:
commit
ba548056ab
|
|
@ -0,0 +1,11 @@
|
||||||
|
### Expected behavior
|
||||||
|
|
||||||
|
### Actual behavior
|
||||||
|
|
||||||
|
### Information about the Issue
|
||||||
|
|
||||||
|
|
||||||
|
### Steps to reproduce the behavior
|
||||||
|
|
||||||
|
1. ...
|
||||||
|
2. ...
|
||||||
|
|
@ -91,7 +91,7 @@ module.exports = function (grunt) {
|
||||||
'FileDescription': WINDOWS_APPNAME,
|
'FileDescription': WINDOWS_APPNAME,
|
||||||
'InternalName': BASENAME + '.exe',
|
'InternalName': BASENAME + '.exe',
|
||||||
'OriginalFilename': BASENAME + '.exe',
|
'OriginalFilename': BASENAME + '.exe',
|
||||||
'LegalCopyright': 'Copyright 2015 Docker Inc. All rights reserved.'
|
'LegalCopyright': 'Copyright 2015-2016 Docker Inc. All rights reserved.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -187,7 +187,7 @@
|
||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2014-2015 Docker, Inc.
|
Copyright 2014-2016 Docker, Inc.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
|
||||||
9
Makefile
9
Makefile
|
|
@ -5,6 +5,11 @@ run:
|
||||||
npm install
|
npm install
|
||||||
npm run
|
npm run
|
||||||
|
|
||||||
|
# Get the IP ADDRESS
|
||||||
|
DOCKER_IP=$(shell python -c "import urlparse ; print urlparse.urlparse('$(DOCKER_HOST)').hostname or ''")
|
||||||
|
HUGO_BASE_URL=$(shell test -z "$(DOCKER_IP)" && echo localhost || echo "$(DOCKER_IP)")
|
||||||
|
HUGO_BIND_IP=0.0.0.0
|
||||||
|
|
||||||
# import the existing docs build cmds from docker/docker
|
# import the existing docs build cmds from docker/docker
|
||||||
DOCS_MOUNT := $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR))
|
DOCS_MOUNT := $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR))
|
||||||
DOCSPORT := 8000
|
DOCSPORT := 8000
|
||||||
|
|
@ -13,7 +18,9 @@ DOCKER_DOCS_IMAGE := kitematic-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||||
DOCKER_RUN_DOCS := docker run --rm -it $(DOCS_MOUNT)
|
DOCKER_RUN_DOCS := docker run --rm -it $(DOCS_MOUNT)
|
||||||
|
|
||||||
docs: docs-build
|
docs: docs-build
|
||||||
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" mkdocs serve
|
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" \
|
||||||
|
hugo server \
|
||||||
|
--port=$(DOCSPORT) --baseUrl=$(HUGO_BASE_URL) --bind=$(HUGO_BIND_IP)
|
||||||
|
|
||||||
docs-shell: docs-build
|
docs-shell: docs-build
|
||||||
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" bash
|
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" bash
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,10 @@ RUN svn checkout https://github.com/docker/compose/trunk/docs /docs/content/comp
|
||||||
RUN svn checkout https://github.com/docker/docker/trunk/docs /docs/content/docker
|
RUN svn checkout https://github.com/docker/docker/trunk/docs /docs/content/docker
|
||||||
RUN svn checkout https://github.com/docker/swarm/trunk/docs /docs/content/swarm
|
RUN svn checkout https://github.com/docker/swarm/trunk/docs /docs/content/swarm
|
||||||
RUN svn checkout https://github.com/docker/distribution/trunk/docs /docs/content/registry
|
RUN svn checkout https://github.com/docker/distribution/trunk/docs /docs/content/registry
|
||||||
RUN svn checkout https://github.com/docker/tutorials/trunk/docs /docs/content
|
RUN svn checkout https://github.com/docker/notary/trunk/docs /docs/content/notary
|
||||||
RUN svn checkout https://github.com/docker/opensource/trunk/docs /docs/content/opensource
|
RUN svn checkout https://github.com/docker/opensource/trunk/docs /docs/content/opensource
|
||||||
RUN svn checkout https://github.com/docker/machine/trunk/docs /docs/content/machine
|
RUN svn checkout https://github.com/docker/machine/trunk/docs /docs/content/machine
|
||||||
|
RUN svn checkout https://github.com/docker/toolbox/trunk/docs /docs/content/toolbox
|
||||||
|
|
||||||
ENV PROJECT=kitematic
|
ENV PROJECT=kitematic
|
||||||
# To get the git info for this repo
|
# To get the git info for this repo
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ title = "Frequently Asked Questions"
|
||||||
description = "Documentation covering common questions users have about Kitematic"
|
description = "Documentation covering common questions users have about Kitematic"
|
||||||
keywords = ["docker, documentation, about, technology, kitematic, gui"]
|
keywords = ["docker, documentation, about, technology, kitematic, gui"]
|
||||||
[menu.main]
|
[menu.main]
|
||||||
parent="smn_workw_kitematic"
|
parent="toolbox_kitematic"
|
||||||
weight=5
|
weight=5
|
||||||
+++
|
+++
|
||||||
<![end-metadata]-->
|
<![end-metadata]-->
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ title = "Kitematic"
|
||||||
description = "Documentation that provides an overview of Kitematic and installation instructions"
|
description = "Documentation that provides an overview of Kitematic and installation instructions"
|
||||||
keywords = ["docker, documentation, about, technology, kitematic, gui"]
|
keywords = ["docker, documentation, about, technology, kitematic, gui"]
|
||||||
[menu.main]
|
[menu.main]
|
||||||
parent="mn_install"
|
identifier="toolbox_kitematic"
|
||||||
|
parent="workw_toolbox"
|
||||||
weight=2
|
weight=2
|
||||||
+++
|
+++
|
||||||
<![end-metadata]-->
|
<![end-metadata]-->
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ title = "Known Issues"
|
||||||
description = "Information about known issues in Kitematic"
|
description = "Information about known issues in Kitematic"
|
||||||
keywords = ["docker, documentation, about, technology, kitematic, gui"]
|
keywords = ["docker, documentation, about, technology, kitematic, gui"]
|
||||||
[menu.main]
|
[menu.main]
|
||||||
parent="smn_workw_kitematic"
|
parent="toolbox_kitematic"
|
||||||
weight=5
|
weight=5
|
||||||
+++
|
+++
|
||||||
<![end-metadata]-->
|
<![end-metadata]-->
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ title = "Set up a Minecraft Server"
|
||||||
description = "Tutorial demonstrating the setup of a Minecraft server using Docker and Kitematic"
|
description = "Tutorial demonstrating the setup of a Minecraft server using Docker and Kitematic"
|
||||||
keywords = ["docker, documentation, about, technology, kitematic, gui, minecraft, tutorial"]
|
keywords = ["docker, documentation, about, technology, kitematic, gui, minecraft, tutorial"]
|
||||||
[menu.main]
|
[menu.main]
|
||||||
parent="smn_workw_kitematic"
|
parent="toolbox_kitematic"
|
||||||
weight=2
|
weight=2
|
||||||
+++
|
+++
|
||||||
<![end-metadata]-->
|
<![end-metadata]-->
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ title = "Set up an Nginx web server"
|
||||||
description = "Tutorial demonstrating the setup of an Nginx web server using Docker and Kitematic"
|
description = "Tutorial demonstrating the setup of an Nginx web server using Docker and Kitematic"
|
||||||
keywords = ["docker, documentation, about, technology, kitematic, gui, nginx, tutorial"]
|
keywords = ["docker, documentation, about, technology, kitematic, gui, nginx, tutorial"]
|
||||||
[menu.main]
|
[menu.main]
|
||||||
parent="smn_workw_kitematic"
|
parent="toolbox_kitematic"
|
||||||
weight=1
|
weight=1
|
||||||
+++
|
+++
|
||||||
<![end-metadata]-->
|
<![end-metadata]-->
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ title = "Creating a Local RethinkDB Database for Development"
|
||||||
description = "Tutorial demonstrating the setup of an RethinkDB database for development"
|
description = "Tutorial demonstrating the setup of an RethinkDB database for development"
|
||||||
keywords = ["docker, documentation, about, technology, kitematic, gui, rethink, tutorial"]
|
keywords = ["docker, documentation, about, technology, kitematic, gui, rethink, tutorial"]
|
||||||
[menu.main]
|
[menu.main]
|
||||||
parent="smn_workw_kitematic"
|
parent="toolbox_kitematic"
|
||||||
weight=3
|
weight=3
|
||||||
+++
|
+++
|
||||||
<![end-metadata]-->
|
<![end-metadata]-->
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ title = "Kitematic User Guide: Intro & Overview"
|
||||||
description = "Documentation that provides an overview of Kitematic and installation instructions"
|
description = "Documentation that provides an overview of Kitematic and installation instructions"
|
||||||
keywords = ["docker, documentation, about, technology, kitematic, gui"]
|
keywords = ["docker, documentation, about, technology, kitematic, gui"]
|
||||||
[menu.main]
|
[menu.main]
|
||||||
parent="smn_workw_kitematic"
|
parent="toolbox_kitematic"
|
||||||
+++
|
+++
|
||||||
<![end-metadata]-->
|
<![end-metadata]-->
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"object-assign",
|
"object-assign",
|
||||||
"underscore",
|
"underscore",
|
||||||
"source-map-support",
|
"source-map-support",
|
||||||
"<rootDir>/node_modules/.*JSONStream"
|
"<rootDir>/node_modules/.*JSONStream",
|
||||||
|
"<rootDir>/node_modules/core-js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "Kitematic",
|
"name": "Kitematic",
|
||||||
"version": "0.9.6",
|
"version": "0.10.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/",
|
||||||
|
|
@ -16,13 +16,11 @@
|
||||||
"test": "jest -c jest-unit.json",
|
"test": "jest -c jest-unit.json",
|
||||||
"integration": "jest -c jest-integration.json",
|
"integration": "jest -c jest-integration.json",
|
||||||
"release": "grunt release",
|
"release": "grunt release",
|
||||||
"release-mac": "grunt release-mac",
|
|
||||||
"lint": "jsxhint src"
|
"lint": "jsxhint src"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"electron-version": "0.35.4",
|
"electron-version": "0.35.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"JSONStream": "^1.0.7",
|
|
||||||
"alt": "^0.16.2",
|
"alt": "^0.16.2",
|
||||||
"ansi-to-html": "0.3.0",
|
"ansi-to-html": "0.3.0",
|
||||||
"any-promise": "^0.1.0",
|
"any-promise": "^0.1.0",
|
||||||
|
|
@ -35,6 +33,7 @@
|
||||||
"dockerode": "^2.2.7",
|
"dockerode": "^2.2.7",
|
||||||
"install": "^0.1.8",
|
"install": "^0.1.8",
|
||||||
"jquery": "^2.1.3",
|
"jquery": "^2.1.3",
|
||||||
|
"JSONStream": "^1.0.7",
|
||||||
"mixpanel": "kitematic/mixpanel-node",
|
"mixpanel": "kitematic/mixpanel-node",
|
||||||
"mkdirp": "^0.5.0",
|
"mkdirp": "^0.5.0",
|
||||||
"node-uuid": "^1.4.3",
|
"node-uuid": "^1.4.3",
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,16 @@ EOF`
|
||||||
function open_iterm () {
|
function open_iterm () {
|
||||||
osascript > /dev/null <<EOF
|
osascript > /dev/null <<EOF
|
||||||
tell application "iTerm"
|
tell application "iTerm"
|
||||||
|
if version < 2.9 then
|
||||||
activate
|
activate
|
||||||
|
try
|
||||||
|
tell current window
|
||||||
|
create tab with default profile
|
||||||
|
tell the current session of current window
|
||||||
|
write text "bash -c \"$CMD\""
|
||||||
|
end tell
|
||||||
|
end tell
|
||||||
|
on error
|
||||||
try
|
try
|
||||||
tell the first terminal
|
tell the first terminal
|
||||||
launch session "Default Session"
|
launch session "Default Session"
|
||||||
|
|
@ -31,6 +40,24 @@ function open_iterm () {
|
||||||
end tell
|
end tell
|
||||||
end tell
|
end tell
|
||||||
end try
|
end try
|
||||||
|
end try
|
||||||
|
end if
|
||||||
|
end tell
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
function open_iterm2point9 () {
|
||||||
|
osascript > /dev/null <<EOF
|
||||||
|
tell application "iTerm"
|
||||||
|
if version ≥ 2.9 then
|
||||||
|
activate
|
||||||
|
tell current window
|
||||||
|
create tab with default profile
|
||||||
|
tell first session of current tab
|
||||||
|
write text "bash -c \"$CMD\""
|
||||||
|
end tell
|
||||||
|
end tell
|
||||||
|
end if
|
||||||
end tell
|
end tell
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +74,7 @@ EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ "$ITERM_EXISTS" == "true" ]; then
|
if [ "$ITERM_EXISTS" == "true" ]; then
|
||||||
open_iterm "$@" || open_terminal "$@"
|
open_iterm2point9 "$@" || open_iterm "$@" || open_terminal "$@"
|
||||||
else
|
else
|
||||||
open_terminal "$@"
|
open_terminal "$@"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,8 @@ class ContainerActions {
|
||||||
this.dispatch();
|
this.dispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
run (name, repo, tag) {
|
run (name, repo, tag, local=false) {
|
||||||
dockerUtil.run(name, repo, tag);
|
dockerUtil.run(name, repo, tag, local);
|
||||||
}
|
}
|
||||||
|
|
||||||
active (name) {
|
active (name) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import alt from '../alt';
|
||||||
|
import dockerUtil from '../utils/DockerUtil';
|
||||||
|
|
||||||
|
class ImageActions {
|
||||||
|
|
||||||
|
all () {
|
||||||
|
this.dispatch({});
|
||||||
|
dockerUtil.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy (image) {
|
||||||
|
dockerUtil.removeImage(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createActions(ImageActions);
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import alt from '../alt';
|
||||||
|
|
||||||
|
class ImageServerActions {
|
||||||
|
constructor () {
|
||||||
|
this.generateActions(
|
||||||
|
'added',
|
||||||
|
'updated',
|
||||||
|
'destroyed',
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createActions(ImageServerActions);
|
||||||
|
|
@ -6,6 +6,11 @@ class SetupActions {
|
||||||
this.dispatch({removeVM});
|
this.dispatch({removeVM});
|
||||||
setupUtil.retry(removeVM);
|
setupUtil.retry(removeVM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useVbox () {
|
||||||
|
this.dispatch({});
|
||||||
|
setupUtil.useVbox();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default alt.createActions(SetupActions);
|
export default alt.createActions(SetupActions);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,10 @@ class TagActions {
|
||||||
this.dispatch({repo});
|
this.dispatch({repo});
|
||||||
regHubUtil.tags(repo);
|
regHubUtil.tags(repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localTags (repo, tags) {
|
||||||
|
this.dispatch({repo, tags});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default alt.createActions(TagActions);
|
export default alt.createActions(TagActions);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
require.main.paths.splice(0, 0, process.env.NODE_PATH);
|
require.main.paths.splice(0, 0, process.env.NODE_PATH);
|
||||||
|
|
||||||
import electron from 'electron';
|
import electron from 'electron';
|
||||||
const remote = electron.remote;
|
const remote = electron.remote;
|
||||||
const Menu = remote.Menu;
|
const Menu = remote.Menu;
|
||||||
|
|
@ -65,8 +66,14 @@ setupUtil.setup().then(() => {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
ipcRenderer.on('application:quitting', () => {
|
ipcRenderer.on('application:quitting', () => {
|
||||||
|
docker.detachEvent();
|
||||||
if (localStorage.getItem('settings.closeVMOnQuit') === 'true') {
|
if (localStorage.getItem('settings.closeVMOnQuit') === 'true') {
|
||||||
machine.stop();
|
machine.stop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.onbeforeunload = function () {
|
||||||
|
docker.detachEvent();
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,20 @@ const BrowserWindow = electron.BrowserWindow;
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import child_process from 'child_process';
|
import child_process from 'child_process';
|
||||||
|
|
||||||
process.env.NODE_PATH = path.join(__dirname, 'node_modules');
|
process.env.NODE_PATH = path.join(__dirname, 'node_modules');
|
||||||
process.env.RESOURCES_PATH = path.join(__dirname, '/../resources');
|
process.env.RESOURCES_PATH = path.join(__dirname, '/../resources');
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
process.env.PATH = '/usr/local/bin:' + process.env.PATH;
|
process.env.PATH = '/usr/local/bin:' + process.env.PATH;
|
||||||
|
}
|
||||||
|
|
||||||
var size = {}, settingsjson = {};
|
var size = {}, settingsjson = {};
|
||||||
try {
|
try {
|
||||||
size = JSON.parse(fs.readFileSync(path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], 'Library', 'Application\ Support', 'Kitematic', 'size')));
|
size = JSON.parse(fs.readFileSync(path.join(app.getPath('userData'), 'size')));
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, 'settings.json'), 'utf8'));
|
settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, 'settings.json'), 'utf8'));
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
|
|
@ -45,6 +47,7 @@ app.on('ready', function () {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (os.platform() === 'win32') {
|
if (os.platform() === 'win32') {
|
||||||
mainWindow.on('close', function () {
|
mainWindow.on('close', function () {
|
||||||
mainWindow.webContents.send('application:quitting');
|
mainWindow.webContents.send('application:quitting');
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ var ContainerHome = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
handleErrorClick: function () {
|
handleErrorClick: function () {
|
||||||
|
// Display wiki for proxy: https://github.com/docker/kitematic/wiki/Common-Proxy-Issues-&-Fixes
|
||||||
shell.openExternal('https://github.com/kitematic/kitematic/issues/new');
|
shell.openExternal('https://github.com/kitematic/kitematic/issues/new');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -52,7 +53,7 @@ var ContainerHome = React.createClass({
|
||||||
if (this.props.container.Error) {
|
if (this.props.container.Error) {
|
||||||
body = (
|
body = (
|
||||||
<div className="details-progress error">
|
<div className="details-progress error">
|
||||||
<h2>We're sorry. There seem to be an error:</h2>
|
<h2>We're sorry. There seems to be an error:</h2>
|
||||||
<p className="error-message">{this.props.container.Error}</p>
|
<p className="error-message">{this.props.container.Error}</p>
|
||||||
<p>If this error is invalid, please file a ticket on our Github repo.</p>
|
<p>If this error is invalid, please file a ticket on our Github repo.</p>
|
||||||
<a className="btn btn-action" onClick={this.handleErrorClick}>File Ticket</a>
|
<a className="btn btn-action" onClick={this.handleErrorClick}>File Ticket</a>
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ var ContainerHomeFolder = React.createClass({
|
||||||
mounts.forEach(m => {
|
mounts.forEach(m => {
|
||||||
if (m.Destination === destination) {
|
if (m.Destination === destination) {
|
||||||
m.Source = util.windowsToLinuxPath(newSource);
|
m.Source = util.windowsToLinuxPath(newSource);
|
||||||
|
m.Driver = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ var ContainerListItem = React.createClass({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router.Link to="container" params={{name: container.Name}}>
|
<Router.Link to="container" params={{name: container.Name}}>
|
||||||
<li onMouseEnter={self.handleItemMouseEnter} onMouseLeave={self.handleItemMouseLeave} onClick={self.handleClick}>
|
<li onMouseEnter={self.handleItemMouseEnter} onMouseLeave={self.handleItemMouseLeave} onClick={self.handleClick} id={this.props.key}>
|
||||||
{state}
|
{state}
|
||||||
<div className="info">
|
<div className="info">
|
||||||
<div className="name">
|
<div className="name">
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ var ContainerSettingsVolumes = React.createClass({
|
||||||
|
|
||||||
metrics.track('Choose Directory for Volume');
|
metrics.track('Choose Directory for Volume');
|
||||||
|
|
||||||
var mounts = _.clone(this.props.container.Mounts);
|
let mounts = _.clone(this.props.container.Mounts);
|
||||||
_.each(mounts, m => {
|
_.each(mounts, m => {
|
||||||
if (m.Destination === dockerVol) {
|
if (m.Destination === dockerVol) {
|
||||||
m.Source = util.windowsToLinuxPath(directory);
|
m.Source = util.windowsToLinuxPath(directory);
|
||||||
|
|
@ -36,7 +36,7 @@ var ContainerSettingsVolumes = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var binds = mounts.map(m => {
|
let binds = mounts.map(m => {
|
||||||
return m.Source + ':' + m.Destination;
|
return m.Source + ':' + m.Destination;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ var ContainerSettingsVolumes = React.createClass({
|
||||||
from: 'settings'
|
from: 'settings'
|
||||||
});
|
});
|
||||||
|
|
||||||
var mounts = _.clone(this.props.container.Mounts);
|
let mounts = _.clone(this.props.container.Mounts);
|
||||||
_.each(mounts, m => {
|
_.each(mounts, m => {
|
||||||
if (m.Destination === dockerVol) {
|
if (m.Destination === dockerVol) {
|
||||||
m.Source = null;
|
m.Source = null;
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ var Containers = React.createClass({
|
||||||
containerStore.listen(this.update);
|
containerStore.listen(this.update);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUnmount: function () {
|
componentWillUnmount: function () {
|
||||||
containerStore.unlisten(this.update);
|
containerStore.unlisten(this.update);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -106,43 +106,7 @@ var Containers = React.createClass({
|
||||||
metrics.track('Opened Issue Reporter', {
|
metrics.track('Opened Issue Reporter', {
|
||||||
from: 'app'
|
from: 'app'
|
||||||
});
|
});
|
||||||
shell.openExternal('https://github.com/kitematic/kitematic/issues/new');
|
shell.openExternal('https://github.com/docker/kitematic/issues/new');
|
||||||
},
|
|
||||||
|
|
||||||
handleMouseEnterDockerTerminal: function () {
|
|
||||||
this.setState({
|
|
||||||
currentButtonLabel: 'Open terminal to use Docker command line.'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleMouseLeaveDockerTerminal: function () {
|
|
||||||
this.setState({
|
|
||||||
currentButtonLabel: ''
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleMouseEnterReportIssue: function () {
|
|
||||||
this.setState({
|
|
||||||
currentButtonLabel: 'Report an issue or suggest feedback.'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleMouseLeaveReportIssue: function () {
|
|
||||||
this.setState({
|
|
||||||
currentButtonLabel: ''
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleMouseEnterPreferences: function () {
|
|
||||||
this.setState({
|
|
||||||
currentButtonLabel: 'Change app preferences.'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleMouseLeavePreferences: function () {
|
|
||||||
this.setState({
|
|
||||||
currentButtonLabel: ''
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function () {
|
render: function () {
|
||||||
|
|
@ -169,9 +133,9 @@ var Containers = React.createClass({
|
||||||
<ContainerList containers={this.state.sorted} newContainer={this.state.newContainer} />
|
<ContainerList containers={this.state.sorted} newContainer={this.state.newContainer} />
|
||||||
</section>
|
</section>
|
||||||
<section className="sidebar-buttons">
|
<section className="sidebar-buttons">
|
||||||
<span className="btn-sidebar btn-terminal" onClick={this.handleClickDockerTerminal} onMouseEnter={this.handleMouseEnterDockerTerminal} onMouseLeave={this.handleMouseLeaveDockerTerminal}><span className="icon icon-docker-cli"></span><span className="text">DOCKER CLI</span></span>
|
<span className="btn-sidebar btn-terminal" onClick={this.handleClickDockerTerminal} ><span className="icon icon-docker-cli"></span><span className="text">DOCKER CLI</span></span>
|
||||||
<span className="btn-sidebar btn-feedback" onClick={this.handleClickReportIssue} onMouseEnter={this.handleMouseEnterDockerTerminal} onMouseLeave={this.handleMouseLeaveDockerTerminal}><span className="icon icon-feedback"></span></span>
|
<span className="btn-sidebar btn-feedback" onClick={this.handleClickReportIssue} ><span className="icon icon-feedback"></span></span>
|
||||||
<span className="btn-sidebar btn-preferences" onClick={this.handleClickPreferences} onMouseEnter={this.handleMouseEnterDockerTerminal} onMouseLeave={this.handleMouseLeaveDockerTerminal}><span className="icon icon-preferences"></span></span>
|
<span className="btn-sidebar btn-preferences" onClick={this.handleClickPreferences} ><span className="icon icon-preferences"></span></span>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<Router.RouteHandler pending={this.state.pending} containers={this.state.containers} container={container}/>
|
<Router.RouteHandler pending={this.state.pending} containers={this.state.containers} container={container}/>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import shell from 'shell';
|
||||||
import RetinaImage from 'react-retina-image';
|
import RetinaImage from 'react-retina-image';
|
||||||
import metrics from '../utils/MetricsUtil';
|
import metrics from '../utils/MetricsUtil';
|
||||||
import containerActions from '../actions/ContainerActions';
|
import containerActions from '../actions/ContainerActions';
|
||||||
|
import imageActions from '../actions/ImageActions';
|
||||||
import containerStore from '../stores/ContainerStore';
|
import containerStore from '../stores/ContainerStore';
|
||||||
import tagStore from '../stores/TagStore';
|
import tagStore from '../stores/TagStore';
|
||||||
import tagActions from '../actions/TagActions';
|
import tagActions from '../actions/TagActions';
|
||||||
|
|
@ -14,8 +15,8 @@ var ImageCard = React.createClass({
|
||||||
mixins: [Router.Navigation],
|
mixins: [Router.Navigation],
|
||||||
getInitialState: function () {
|
getInitialState: function () {
|
||||||
return {
|
return {
|
||||||
tags: [],
|
tags: this.props.tags || [],
|
||||||
chosenTag: 'latest'
|
chosenTag: this.props.chosenTag || 'latest'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentDidMount: function () {
|
componentDidMount: function () {
|
||||||
|
|
@ -49,11 +50,14 @@ var ImageCard = React.createClass({
|
||||||
private: this.props.image.is_private,
|
private: this.props.image.is_private,
|
||||||
official: this.props.image.namespace === 'library',
|
official: this.props.image.namespace === 'library',
|
||||||
userowned: this.props.image.is_user_repo,
|
userowned: this.props.image.is_user_repo,
|
||||||
recommended: this.props.image.is_recommended
|
recommended: this.props.image.is_recommended,
|
||||||
|
local: this.props.image.is_local || false
|
||||||
});
|
});
|
||||||
let name = containerStore.generateName(this.props.image.name);
|
let name = containerStore.generateName(this.props.image.name);
|
||||||
let repo = this.props.image.namespace === 'library' ? this.props.image.name : this.props.image.namespace + '/' + this.props.image.name;
|
let localImage = this.props.image.is_local || false;
|
||||||
containerActions.run(name, repo, this.state.chosenTag);
|
let repo = (this.props.image.namespace === 'library' || this.props.image.namespace === 'local') ? this.props.image.name : this.props.image.namespace + '/' + this.props.image.name;
|
||||||
|
|
||||||
|
containerActions.run(name, repo, this.state.chosenTag, localImage);
|
||||||
this.transitionTo('containerHome', {name});
|
this.transitionTo('containerHome', {name});
|
||||||
},
|
},
|
||||||
handleMenuOverlayClick: function () {
|
handleMenuOverlayClick: function () {
|
||||||
|
|
@ -67,7 +71,12 @@ var ImageCard = React.createClass({
|
||||||
handleTagOverlayClick: function () {
|
handleTagOverlayClick: function () {
|
||||||
let $tagOverlay = $(this.getDOMNode()).find('.tag-overlay');
|
let $tagOverlay = $(this.getDOMNode()).find('.tag-overlay');
|
||||||
$tagOverlay.fadeIn(300);
|
$tagOverlay.fadeIn(300);
|
||||||
|
let localImage = this.props.image.is_local || false;
|
||||||
|
if (localImage) {
|
||||||
|
tagActions.localTags(this.props.image.namespace + '/' + this.props.image.name, this.props.tags);
|
||||||
|
} else {
|
||||||
tagActions.tags(this.props.image.namespace + '/' + this.props.image.name);
|
tagActions.tags(this.props.image.namespace + '/' + this.props.image.name);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
handleCloseTagOverlay: function () {
|
handleCloseTagOverlay: function () {
|
||||||
let $menuOverlay = $(this.getDOMNode()).find('.menu-overlay');
|
let $menuOverlay = $(this.getDOMNode()).find('.menu-overlay');
|
||||||
|
|
@ -75,12 +84,17 @@ var ImageCard = React.createClass({
|
||||||
var $tagOverlay = $(this.getDOMNode()).find('.tag-overlay');
|
var $tagOverlay = $(this.getDOMNode()).find('.tag-overlay');
|
||||||
$tagOverlay.fadeOut(300);
|
$tagOverlay.fadeOut(300);
|
||||||
},
|
},
|
||||||
|
handleDeleteImgClick: function (image) {
|
||||||
|
if (this.state.chosenTag && !this.props.image.inUse) {
|
||||||
|
imageActions.destroy(image.RepoTags[0].split(':')[0] + ':' + this.state.chosenTag);
|
||||||
|
}
|
||||||
|
},
|
||||||
handleRepoClick: function () {
|
handleRepoClick: function () {
|
||||||
var repoUri = 'https://registry.hub.docker.com/';
|
var repoUri = 'https://hub.docker.com/';
|
||||||
if (this.props.image.namespace === 'library') {
|
if (this.props.image.namespace === 'library') {
|
||||||
repoUri = repoUri + '_/' + this.props.image.name;
|
repoUri = repoUri + '_/' + this.props.image.name;
|
||||||
} else {
|
} else {
|
||||||
repoUri = repoUri + 'u/' + this.props.image.namespace + '/' + this.props.image.name;
|
repoUri = repoUri + 'r/' + this.props.image.namespace + '/' + this.props.image.name;
|
||||||
}
|
}
|
||||||
shell.openExternal(repoUri);
|
shell.openExternal(repoUri);
|
||||||
},
|
},
|
||||||
|
|
@ -105,11 +119,12 @@ var ImageCard = React.createClass({
|
||||||
var description;
|
var description;
|
||||||
if (this.props.image.description) {
|
if (this.props.image.description) {
|
||||||
description = this.props.image.description;
|
description = this.props.image.description;
|
||||||
|
} else if(this.props.image.short_description){
|
||||||
|
description = this.props.image.short_description;
|
||||||
} else {
|
} else {
|
||||||
description = "No description.";
|
description = 'No description.';
|
||||||
}
|
}
|
||||||
var logoStyle = {
|
var logoStyle = {
|
||||||
//backgroundImage: `linear-gradient(-180deg, ${this.props.image.gradient_start} 4%, ${this.props.image.gradient_end} 100%)`
|
|
||||||
backgroundColor: this.props.image.gradient_start
|
backgroundColor: this.props.image.gradient_start
|
||||||
};
|
};
|
||||||
var imgsrc;
|
var imgsrc;
|
||||||
|
|
@ -148,10 +163,58 @@ var ImageCard = React.createClass({
|
||||||
<span className="icon icon-badge-private"></span>
|
<span className="icon icon-badge-private"></span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let create, overlay;
|
||||||
|
if (this.props.image.is_local) {
|
||||||
|
create = (
|
||||||
|
<div className="actions">
|
||||||
|
<div className="favorites">
|
||||||
|
<span className="icon icon-tag"> {this.state.chosenTag}</span>
|
||||||
|
<span className="text"></span>
|
||||||
|
</div>
|
||||||
|
<div className="more-menu" onClick={self.handleMenuOverlayClick}>
|
||||||
|
<span className="icon icon-more"></span>
|
||||||
|
</div>
|
||||||
|
<div className="action" onClick={self.handleClick}>
|
||||||
|
CREATE
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
overlay = (
|
||||||
|
<div className="overlay menu-overlay">
|
||||||
|
<div className="menu-item" onClick={this.handleTagOverlayClick.bind(this, this.props.image.name)}>
|
||||||
|
<span className="icon icon-tag"></span><span className="text">SELECTED TAG: <span className="selected-tag">{this.state.chosenTag}</span></span>
|
||||||
|
</div>
|
||||||
|
<div className="remove" onClick={this.handleDeleteImgClick.bind(this, this.props.image)}>
|
||||||
|
<span className="btn btn-delete btn-action has-icon btn-hollow" disabled={this.props.image.inUse ? 'disabled' : null}><span className="icon icon-delete"></span>Delete Tag</span>
|
||||||
|
</div>
|
||||||
|
{this.props.image.inUse ? <p className="small">To delete, remove all containers<br/>using the above image</p> : null }
|
||||||
|
<div className="close-overlay">
|
||||||
|
<a className="btn btn-action circular" onClick={self.handleCloseMenuOverlay}><span className="icon icon-delete"></span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
let favCount = (this.props.image.star_count < 1000) ? numeral(this.props.image.star_count).value() : numeral(this.props.image.star_count).format('0.0a').toUpperCase();
|
let favCount = (this.props.image.star_count < 1000) ? numeral(this.props.image.star_count).value() : numeral(this.props.image.star_count).format('0.0a').toUpperCase();
|
||||||
let pullCount = (this.props.image.pull_count < 1000) ? numeral(this.props.image.pull_count).value() : numeral(this.props.image.pull_count).format('0a').toUpperCase();
|
let pullCount = (this.props.image.pull_count < 1000) ? numeral(this.props.image.pull_count).value() : numeral(this.props.image.pull_count).format('0a').toUpperCase();
|
||||||
return (
|
create = (
|
||||||
<div className="image-item">
|
<div className="actions">
|
||||||
|
<div className="favorites">
|
||||||
|
<span className="icon icon-favorite"></span>
|
||||||
|
<span className="text">{favCount}</span>
|
||||||
|
<span className="icon icon-download"></span>
|
||||||
|
<span className="text">{pullCount}</span>
|
||||||
|
</div>
|
||||||
|
<div className="more-menu" onClick={self.handleMenuOverlayClick}>
|
||||||
|
<span className="icon icon-more"></span>
|
||||||
|
</div>
|
||||||
|
<div className="action" onClick={self.handleClick}>
|
||||||
|
CREATE
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
overlay = (
|
||||||
<div className="overlay menu-overlay">
|
<div className="overlay menu-overlay">
|
||||||
<div className="menu-item" onClick={this.handleTagOverlayClick.bind(this, this.props.image.name)}>
|
<div className="menu-item" onClick={this.handleTagOverlayClick.bind(this, this.props.image.name)}>
|
||||||
<span className="icon icon-tag"></span><span className="text">SELECTED TAG: <span className="selected-tag">{this.state.chosenTag}</span></span>
|
<span className="icon icon-tag"></span><span className="text">SELECTED TAG: <span className="selected-tag">{this.state.chosenTag}</span></span>
|
||||||
|
|
@ -163,6 +226,11 @@ var ImageCard = React.createClass({
|
||||||
<a className="btn btn-action circular" onClick={self.handleCloseMenuOverlay}><span className="icon icon-delete"></span></a>
|
<a className="btn btn-action circular" onClick={self.handleCloseMenuOverlay}><span className="icon icon-delete"></span></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="image-item">
|
||||||
|
{overlay}
|
||||||
<div className="overlay tag-overlay">
|
<div className="overlay tag-overlay">
|
||||||
<p>Please select an image tag.</p>
|
<p>Please select an image tag.</p>
|
||||||
{tags}
|
{tags}
|
||||||
|
|
@ -185,20 +253,7 @@ var ImageCard = React.createClass({
|
||||||
{description}
|
{description}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="actions">
|
{create}
|
||||||
<div className="favorites">
|
|
||||||
<span className="icon icon-favorite"></span>
|
|
||||||
<span className="text">{favCount}</span>
|
|
||||||
<span className="icon icon-download"></span>
|
|
||||||
<span className="text">{pullCount}</span>
|
|
||||||
</div>
|
|
||||||
<div className="more-menu" onClick={self.handleMenuOverlayClick}>
|
|
||||||
<span className="icon icon-more"></span>
|
|
||||||
</div>
|
|
||||||
<div className="action" onClick={self.handleClick}>
|
|
||||||
CREATE
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import repositoryActions from '../actions/RepositoryActions';
|
||||||
import repositoryStore from '../stores/RepositoryStore';
|
import repositoryStore from '../stores/RepositoryStore';
|
||||||
import accountStore from '../stores/AccountStore';
|
import accountStore from '../stores/AccountStore';
|
||||||
import accountActions from '../actions/AccountActions';
|
import accountActions from '../actions/AccountActions';
|
||||||
|
import imageActions from '../actions/ImageActions';
|
||||||
|
import imageStore from '../stores/ImageStore';
|
||||||
|
|
||||||
var _searchPromise = null;
|
var _searchPromise = null;
|
||||||
|
|
||||||
|
|
@ -20,6 +22,8 @@ module.exports = React.createClass({
|
||||||
query: '',
|
query: '',
|
||||||
loading: repositoryStore.loading(),
|
loading: repositoryStore.loading(),
|
||||||
repos: repositoryStore.all(),
|
repos: repositoryStore.all(),
|
||||||
|
images: imageStore.all(),
|
||||||
|
imagesErr: imageStore.error,
|
||||||
username: accountStore.getState().username,
|
username: accountStore.getState().username,
|
||||||
verified: accountStore.getState().verified,
|
verified: accountStore.getState().verified,
|
||||||
accountLoading: accountStore.getState().loading,
|
accountLoading: accountStore.getState().loading,
|
||||||
|
|
@ -34,6 +38,7 @@ module.exports = React.createClass({
|
||||||
this.refs.searchInput.getDOMNode().focus();
|
this.refs.searchInput.getDOMNode().focus();
|
||||||
repositoryStore.listen(this.update);
|
repositoryStore.listen(this.update);
|
||||||
accountStore.listen(this.updateAccount);
|
accountStore.listen(this.updateAccount);
|
||||||
|
imageStore.listen(this.updateImage);
|
||||||
repositoryActions.search();
|
repositoryActions.search();
|
||||||
},
|
},
|
||||||
componentWillUnmount: function () {
|
componentWillUnmount: function () {
|
||||||
|
|
@ -51,7 +56,14 @@ module.exports = React.createClass({
|
||||||
currentPage: repositoryStore.getState().currentPage,
|
currentPage: repositoryStore.getState().currentPage,
|
||||||
totalPage: repositoryStore.getState().totalPage,
|
totalPage: repositoryStore.getState().totalPage,
|
||||||
previousPage: repositoryStore.getState().previousPage,
|
previousPage: repositoryStore.getState().previousPage,
|
||||||
nextPage: repositoryStore.getState().nextPage
|
nextPage: repositoryStore.getState().nextPage,
|
||||||
|
error: repositoryStore.getState().error
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updateImage: function (imgStore) {
|
||||||
|
this.setState({
|
||||||
|
images: imgStore.images,
|
||||||
|
error: imgStore.error
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateAccount: function () {
|
updateAccount: function () {
|
||||||
|
|
@ -73,14 +85,14 @@ module.exports = React.createClass({
|
||||||
nextPage = (page + 1 > this.state.totalPage) ? this.state.totalPage : page + 1;
|
nextPage = (page + 1 > this.state.totalPage) ? this.state.totalPage : page + 1;
|
||||||
totalPage = this.state.totalPage;
|
totalPage = this.state.totalPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
query: query,
|
query: query,
|
||||||
loading: true,
|
loading: true,
|
||||||
currentPage: page,
|
currentPage: page,
|
||||||
previousPage: previousPage,
|
previousPage: previousPage,
|
||||||
nextPage: nextPage,
|
nextPage: nextPage,
|
||||||
totalPage: totalPage
|
totalPage: totalPage,
|
||||||
|
error: null
|
||||||
});
|
});
|
||||||
|
|
||||||
_searchPromise = Promise.delay(200).cancellable().then(() => {
|
_searchPromise = Promise.delay(200).cancellable().then(() => {
|
||||||
|
|
@ -102,11 +114,17 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
handleFilter: function (filter) {
|
handleFilter: function (filter) {
|
||||||
|
|
||||||
|
this.setState({error: null});
|
||||||
|
|
||||||
// If we're clicking on the filter again - refresh
|
// If we're clicking on the filter again - refresh
|
||||||
if (filter === 'userrepos' && this.getQuery().filter === 'userrepos') {
|
if (filter === 'userrepos' && this.getQuery().filter === 'userrepos') {
|
||||||
repositoryActions.repos();
|
repositoryActions.repos();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filter === 'userimages' && this.getQuery().filter === 'userimages') {
|
||||||
|
imageActions.all();
|
||||||
|
}
|
||||||
|
|
||||||
if (filter === 'recommended' && this.getQuery().filter === 'recommended') {
|
if (filter === 'recommended' && this.getQuery().filter === 'recommended') {
|
||||||
repositoryActions.recommended();
|
repositoryActions.recommended();
|
||||||
}
|
}
|
||||||
|
|
@ -188,10 +206,16 @@ module.exports = React.createClass({
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
) : null;
|
) : null;
|
||||||
|
let errorMsg = null;
|
||||||
|
if (this.state.error === null || this.state.error.message.indexOf('getaddrinfo ENOTFOUND') !== -1) {
|
||||||
|
errorMsg = 'There was an error contacting Docker Hub.';
|
||||||
|
} else {
|
||||||
|
errorMsg = this.state.error.message.replace('HTTP code is 409 which indicates error: conflict - ', '');
|
||||||
|
}
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
results = (
|
results = (
|
||||||
<div className="no-results">
|
<div className="no-results">
|
||||||
<h2>There was an error contacting Docker Hub.</h2>
|
<h2 className="error">{errorMsg}</h2>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
paginateResults = null;
|
paginateResults = null;
|
||||||
|
|
@ -215,6 +239,34 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
paginateResults = null;
|
paginateResults = null;
|
||||||
|
} else if (filter === 'userimages') {
|
||||||
|
let userImageItems = this.state.images.map(image => {
|
||||||
|
let repo = image.RepoTags[0].split(':')[0];
|
||||||
|
if (repo.indexOf('/') === -1) {
|
||||||
|
repo = 'local/' + repo;
|
||||||
|
}
|
||||||
|
[image.namespace, image.name] = repo.split('/');
|
||||||
|
image.description = null;
|
||||||
|
let tags = image.tags.join('-');
|
||||||
|
image.star_count = 0;
|
||||||
|
image.is_local = true;
|
||||||
|
return (<ImageCard key={image.namespace + '/' + image.name + ':' + tags} image={image} chosenTag={image.tags[0]} tags={image.tags} />);
|
||||||
|
});
|
||||||
|
let userImageResults = userImageItems.length ? (
|
||||||
|
<div className="result-grids">
|
||||||
|
<div>
|
||||||
|
<h4>My Images</h4>
|
||||||
|
<div className="result-grid">
|
||||||
|
{userImageItems}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : <div className="no-results">
|
||||||
|
<h2>Cannot find any local image.</h2>
|
||||||
|
</div>;
|
||||||
|
results = (
|
||||||
|
{userImageResults}
|
||||||
|
);
|
||||||
} else if (this.state.loading) {
|
} else if (this.state.loading) {
|
||||||
results = (
|
results = (
|
||||||
<div className="no-results">
|
<div className="no-results">
|
||||||
|
|
@ -300,13 +352,19 @@ module.exports = React.createClass({
|
||||||
'icon-search': true,
|
'icon-search': true,
|
||||||
'search-icon': true
|
'search-icon': true
|
||||||
});
|
});
|
||||||
|
let searchClasses = classNames('search-bar');
|
||||||
|
if (filter === 'userimages') {
|
||||||
|
searchClasses = classNames('search-bar', {
|
||||||
|
hidden: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="details">
|
<div className="details">
|
||||||
<div className="new-container">
|
<div className="new-container">
|
||||||
<div className="new-container-header">
|
<div className="new-container-header">
|
||||||
<div className="search">
|
<div className="search">
|
||||||
<div className="search-bar">
|
<div className={searchClasses}>
|
||||||
<input type="search" ref="searchInput" className="form-control" placeholder="Search for Docker images from Docker Hub" onChange={this.handleChange}/>
|
<input type="search" ref="searchInput" className="form-control" placeholder="Search for Docker images from Docker Hub" onChange={this.handleChange}/>
|
||||||
<div className={magnifierClasses}></div>
|
<div className={magnifierClasses}></div>
|
||||||
<div className={loadingClasses}><div></div></div>
|
<div className={loadingClasses}><div></div></div>
|
||||||
|
|
@ -317,6 +375,7 @@ module.exports = React.createClass({
|
||||||
<span className={`results-filter results-all tab ${filter === 'all' ? 'active' : ''}`} onClick={this.handleFilter.bind(this, 'all')}>All</span>
|
<span className={`results-filter results-all tab ${filter === 'all' ? 'active' : ''}`} onClick={this.handleFilter.bind(this, 'all')}>All</span>
|
||||||
<span className={`results-filter results-recommended tab ${filter === 'recommended' ? 'active' : ''}`} onClick={this.handleFilter.bind(this, 'recommended')}>Recommended</span>
|
<span className={`results-filter results-recommended tab ${filter === 'recommended' ? 'active' : ''}`} onClick={this.handleFilter.bind(this, 'recommended')}>Recommended</span>
|
||||||
<span className={`results-filter results-userrepos tab ${filter === 'userrepos' ? 'active' : ''}`} onClick={this.handleFilter.bind(this, 'userrepos')}>My Repos</span>
|
<span className={`results-filter results-userrepos tab ${filter === 'userrepos' ? 'active' : ''}`} onClick={this.handleFilter.bind(this, 'userrepos')}>My Repos</span>
|
||||||
|
<span className={`results-filter results-userimages tab ${filter === 'userimages' ? 'active' : ''}`} onClick={this.handleFilter.bind(this, 'userimages')}>My Images</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="results">
|
<div className="results">
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ import Router from 'react-router';
|
||||||
import Radial from './Radial.react.js';
|
import Radial from './Radial.react.js';
|
||||||
import RetinaImage from 'react-retina-image';
|
import RetinaImage from 'react-retina-image';
|
||||||
import Header from './Header.react';
|
import Header from './Header.react';
|
||||||
import Util from '../utils/Util';
|
import util from '../utils/Util';
|
||||||
import metrics from '../utils/MetricsUtil';
|
import metrics from '../utils/MetricsUtil';
|
||||||
import setupStore from '../stores/SetupStore';
|
import setupStore from '../stores/SetupStore';
|
||||||
import setupActions from '../actions/SetupActions';
|
import setupActions from '../actions/SetupActions';
|
||||||
import shell from 'shell';
|
import shell from 'shell';
|
||||||
|
|
||||||
|
|
||||||
var Setup = React.createClass({
|
var Setup = React.createClass({
|
||||||
mixins: [Router.Navigation],
|
mixins: [Router.Navigation],
|
||||||
|
|
||||||
|
|
@ -20,7 +21,7 @@ var Setup = React.createClass({
|
||||||
setupStore.listen(this.update);
|
setupStore.listen(this.update);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUnmount: function () {
|
componentWillUnmount: function () {
|
||||||
setupStore.unlisten(this.update);
|
setupStore.unlisten(this.update);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -32,6 +33,10 @@ var Setup = React.createClass({
|
||||||
setupActions.retry(false);
|
setupActions.retry(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleUseVbox: function () {
|
||||||
|
setupActions.useVbox();
|
||||||
|
},
|
||||||
|
|
||||||
handleErrorRemoveRetry: function () {
|
handleErrorRemoveRetry: function () {
|
||||||
console.log('Deleting VM and trying again.' );
|
console.log('Deleting VM and trying again.' );
|
||||||
setupActions.retry(true);
|
setupActions.retry(true);
|
||||||
|
|
@ -63,6 +68,12 @@ var Setup = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
renderProgress: function () {
|
renderProgress: function () {
|
||||||
|
let title = 'Starting Docker VM';
|
||||||
|
let descr = 'To run Docker containers on your computer, Kitematic is starting a Linux virtual machine. This may take a minute...';
|
||||||
|
if (util.isNative()) {
|
||||||
|
title = 'Checking Docker';
|
||||||
|
descr = 'To run Docker containers on your computer, Kitematic is checking the Docker connection.';
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="setup">
|
<div className="setup">
|
||||||
<Header hideLogin={true}/>
|
<Header hideLogin={true}/>
|
||||||
|
|
@ -72,8 +83,8 @@ var Setup = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
<div className="desc">
|
<div className="desc">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<h1>Starting Docker VM</h1>
|
<h1>{title}</h1>
|
||||||
<p>To run Docker containers on your computer, Kitematic is starting a Linux virtual machine. This may take a minute...</p>
|
<p>{descr}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -84,14 +95,17 @@ var Setup = React.createClass({
|
||||||
renderError: function () {
|
renderError: function () {
|
||||||
let deleteVmAndRetry;
|
let deleteVmAndRetry;
|
||||||
|
|
||||||
if (Util.isLinux()) {
|
if (util.isLinux()) {
|
||||||
if (!this.state.started) {
|
if (!this.state.started) {
|
||||||
deleteVmAndRetry = (
|
deleteVmAndRetry = (
|
||||||
<button className="btn btn-action" onClick={this.handleLinuxDockerInstall}>Install Docker</button>
|
<button className="btn btn-action" onClick={this.handleLinuxDockerInstall}>Install Docker</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (util.isNative()) {
|
||||||
if (this.state.started) {
|
deleteVmAndRetry = (
|
||||||
|
<button className="btn btn-action" onClick={this.handleUseVbox}>Use VirtualBox</button>
|
||||||
|
);
|
||||||
|
} else if (this.state.started) {
|
||||||
deleteVmAndRetry = (
|
deleteVmAndRetry = (
|
||||||
<button className="btn btn-action" onClick={this.handleErrorRemoveRetry}>Delete VM & Retry Setup</button>
|
<button className="btn btn-action" onClick={this.handleErrorRemoveRetry}>Delete VM & Retry Setup</button>
|
||||||
);
|
);
|
||||||
|
|
@ -100,7 +114,6 @@ var Setup = React.createClass({
|
||||||
<button className="btn btn-action" onClick={this.handleToolBox}>Get Toolbox</button>
|
<button className="btn btn-action" onClick={this.handleToolBox}>Get Toolbox</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div className="setup">
|
<div className="setup">
|
||||||
<Header hideLogin={true}/>
|
<Header hideLogin={true}/>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import alt from '../alt';
|
||||||
|
import imageActions from '../actions/ImageActions';
|
||||||
|
import imageServerActions from '../actions/ImageServerActions';
|
||||||
|
|
||||||
|
class ImageStore {
|
||||||
|
constructor () {
|
||||||
|
this.bindActions(imageActions);
|
||||||
|
this.bindActions(imageServerActions);
|
||||||
|
this.results = [];
|
||||||
|
this.images = [];
|
||||||
|
this.imagesLoading = false;
|
||||||
|
this.resultsLoading = false;
|
||||||
|
this.error = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
error (error) {
|
||||||
|
this.setState({error: error, imagesLoading: false, resultsLoading: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearError () {
|
||||||
|
this.setState({error: null});
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyed (data) {
|
||||||
|
let images = this.images;
|
||||||
|
if ((data && data[1] && data[1].Deleted)) {
|
||||||
|
delete images[data[1].Deleted];
|
||||||
|
}
|
||||||
|
this.setState({error: null});
|
||||||
|
}
|
||||||
|
|
||||||
|
updated (images) {
|
||||||
|
let tags = {};
|
||||||
|
let finalImages = [];
|
||||||
|
images.map((image) => {
|
||||||
|
image.RepoTags.map(repoTags => {
|
||||||
|
let [name, tag] = repoTags.split(':');
|
||||||
|
if (typeof tags[name] !== 'undefined') {
|
||||||
|
finalImages[tags[name]].tags.push(tag);
|
||||||
|
if (image.inUse) {
|
||||||
|
finalImages[tags[name]].inUse = image.inUse;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
image.tags = [tag];
|
||||||
|
tags[name] = finalImages.length;
|
||||||
|
finalImages.push(image);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.setState({error: null, images: finalImages, imagesLoading: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
static all () {
|
||||||
|
let state = this.getState();
|
||||||
|
return state.images;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default alt.createStore(ImageStore);
|
||||||
|
|
@ -21,6 +21,15 @@ class TagStore {
|
||||||
this.emitChange();
|
this.emitChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localTags ({repo, tags}) {
|
||||||
|
let data = [];
|
||||||
|
tags.map((value) => {
|
||||||
|
data.push({'name': value});
|
||||||
|
});
|
||||||
|
this.loading[repo] = true;
|
||||||
|
this.tagsUpdated({repo, tags: data || []});
|
||||||
|
}
|
||||||
|
|
||||||
tagsUpdated ({repo, tags}) {
|
tagsUpdated ({repo, tags}) {
|
||||||
this.tags[repo] = tags;
|
this.tags[repo] = tags;
|
||||||
this.loading[repo] = false;
|
this.loading[repo] = false;
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ var ContainerUtil = {
|
||||||
var [dockerPort, portType] = key.split('/');
|
var [dockerPort, portType] = key.split('/');
|
||||||
var localUrl = null;
|
var localUrl = null;
|
||||||
var port = null;
|
var port = null;
|
||||||
|
|
||||||
if (value && value.length) {
|
if (value && value.length) {
|
||||||
port = value[0].HostPort;
|
port = value[0].HostPort;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,11 +158,13 @@ var DockerMachine = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (util.isLinux()) {
|
} else if (util.isNative()) {
|
||||||
cmd = cmd || process.env.SHELL;
|
cmd = cmd || process.env.SHELL;
|
||||||
var terminal = util.linuxTerminal();
|
var terminal = util.isLinux() ? util.linuxTerminal() : [path.join(process.env.RESOURCES_PATH, 'terminal')];
|
||||||
if (terminal)
|
terminal.push(cmd);
|
||||||
util.execFile(terminal.concat([cmd])).then(() => {});
|
if (terminal) {
|
||||||
|
util.execFile(terminal).then(() => {});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
cmd = cmd || process.env.SHELL;
|
cmd = cmd || process.env.SHELL;
|
||||||
this.url(machineName).then(machineUrl => {
|
this.url(machineName).then(machineUrl => {
|
||||||
|
|
|
||||||
|
|
@ -8,32 +8,42 @@ import util from './Util';
|
||||||
import hubUtil from './HubUtil';
|
import hubUtil from './HubUtil';
|
||||||
import metrics from '../utils/MetricsUtil';
|
import metrics from '../utils/MetricsUtil';
|
||||||
import containerServerActions from '../actions/ContainerServerActions';
|
import containerServerActions from '../actions/ContainerServerActions';
|
||||||
|
import imageServerActions from '../actions/ImageServerActions';
|
||||||
|
import Promise from 'bluebird';
|
||||||
import rimraf from 'rimraf';
|
import rimraf from 'rimraf';
|
||||||
import stream from 'stream';
|
import stream from 'stream';
|
||||||
import JSONStream from 'JSONStream';
|
import JSONStream from 'JSONStream';
|
||||||
|
|
||||||
export default {
|
|
||||||
|
|
||||||
|
var DockerUtil = {
|
||||||
host: null,
|
host: null,
|
||||||
client: null,
|
client: null,
|
||||||
placeholders: {},
|
placeholders: {},
|
||||||
streams: {},
|
stream: null,
|
||||||
|
eventStream: null,
|
||||||
activeContainerName: null,
|
activeContainerName: null,
|
||||||
|
localImages: null,
|
||||||
|
imagesUsed: [],
|
||||||
|
|
||||||
setup (ip, name) {
|
setup (ip, name) {
|
||||||
if (!ip || !name) {
|
if (!ip && !name) {
|
||||||
throw new Error('Falsy ip or name passed to docker client setup');
|
throw new Error('Falsy ip or name passed to docker client setup');
|
||||||
}
|
}
|
||||||
|
this.host = ip;
|
||||||
|
|
||||||
if (util.isLinux()) {
|
if (ip.indexOf('local') !== -1) {
|
||||||
this.host = 'localhost';
|
try {
|
||||||
this.client = new dockerode({socketPath: '/var/run/docker.sock'});
|
this.client = new dockerode({socketPath: '/var/run/docker.sock'});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Cannot connect to the Docker daemon. Is the daemon running?');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let certDir = path.join(util.home(), '.docker/machine/machines/', name);
|
let certDir = path.join(util.home(), '.docker/machine/machines/', name);
|
||||||
if (!fs.existsSync(certDir)) {
|
if (!fs.existsSync(certDir)) {
|
||||||
throw new Error('Certificate directory does not exist');
|
throw new Error('Certificate directory does not exist');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.host = ip;
|
|
||||||
this.client = new dockerode({
|
this.client = new dockerode({
|
||||||
protocol: 'https',
|
protocol: 'https',
|
||||||
host: ip,
|
host: ip,
|
||||||
|
|
@ -45,9 +55,31 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async version () {
|
||||||
|
let version = null;
|
||||||
|
let maxRetries = 10;
|
||||||
|
let retries = 0;
|
||||||
|
let error_message = "";
|
||||||
|
while (version == null && retries < maxRetries) {
|
||||||
|
this.client.version((error,data) => {
|
||||||
|
if (!error) {
|
||||||
|
version = data.Version;
|
||||||
|
} else {
|
||||||
|
error_message = error;
|
||||||
|
}
|
||||||
|
retries++;
|
||||||
|
});
|
||||||
|
await Promise.delay(1000);
|
||||||
|
}
|
||||||
|
if (version == null) {
|
||||||
|
throw new Error(error_message);
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
},
|
||||||
|
|
||||||
init () {
|
init () {
|
||||||
this.placeholders = JSON.parse(localStorage.getItem('placeholders')) || {};
|
this.placeholders = JSON.parse(localStorage.getItem('placeholders')) || {};
|
||||||
this.fetchAllContainers();
|
this.refresh();
|
||||||
this.listen();
|
this.listen();
|
||||||
|
|
||||||
// Resume pulling containers that were previously being pulled
|
// Resume pulling containers that were previously being pulled
|
||||||
|
|
@ -88,6 +120,7 @@ export default {
|
||||||
container.start((error) => {
|
container.start((error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
containerServerActions.error({name, error});
|
containerServerActions.error({name, error});
|
||||||
|
console.log('error starting: %o - %o', name, error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
containerServerActions.started({name, error});
|
containerServerActions.started({name, error});
|
||||||
|
|
@ -121,7 +154,7 @@ export default {
|
||||||
if (image.Config.Cmd) {
|
if (image.Config.Cmd) {
|
||||||
containerData.Cmd = image.Config.Cmd;
|
containerData.Cmd = image.Config.Cmd;
|
||||||
} else if (!image.Config.Entrypoint) {
|
} else if (!image.Config.Entrypoint) {
|
||||||
containerData.Cmd = 'bash';
|
containerData.Cmd = 'sh';
|
||||||
}
|
}
|
||||||
|
|
||||||
let existing = this.client.getContainer(name);
|
let existing = this.client.getContainer(name);
|
||||||
|
|
@ -136,6 +169,7 @@ export default {
|
||||||
this.startContainer(name);
|
this.startContainer(name);
|
||||||
delete this.placeholders[name];
|
delete this.placeholders[name];
|
||||||
localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
|
localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
|
||||||
|
this.refresh();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -156,14 +190,20 @@ export default {
|
||||||
fetchAllContainers () {
|
fetchAllContainers () {
|
||||||
this.client.listContainers({all: true}, (err, containers) => {
|
this.client.listContainers({all: true}, (err, containers) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.imagesUsed = [];
|
||||||
async.map(containers, (container, callback) => {
|
async.map(containers, (container, callback) => {
|
||||||
this.client.getContainer(container.Id).inspect((error, container) => {
|
this.client.getContainer(container.Id).inspect((error, container) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
callback(null, null);
|
callback(null, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let imgSha = container.Image.replace('sha256:', '');
|
||||||
|
if (_.indexOf(this.imagesUsed, imgSha) === -1) {
|
||||||
|
this.imagesUsed.push(imgSha);
|
||||||
|
}
|
||||||
container.Name = container.Name.replace('/', '');
|
container.Name = container.Name.replace('/', '');
|
||||||
callback(null, container);
|
callback(null, container);
|
||||||
});
|
});
|
||||||
|
|
@ -171,15 +211,55 @@ export default {
|
||||||
containers = containers.filter(c => c !== null);
|
containers = containers.filter(c => c !== null);
|
||||||
if (err) {
|
if (err) {
|
||||||
// TODO: add a global error handler for this
|
// TODO: add a global error handler for this
|
||||||
|
console.error(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
containerServerActions.allUpdated({containers: _.indexBy(containers.concat(_.values(this.placeholders)), 'Name')});
|
containerServerActions.allUpdated({containers: _.indexBy(containers.concat(_.values(this.placeholders)), 'Name')});
|
||||||
this.logs();
|
this.logs();
|
||||||
|
this.fetchAllImages();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
run (name, repository, tag) {
|
fetchAllImages () {
|
||||||
|
this.client.listImages((err, list) => {
|
||||||
|
if (err) {
|
||||||
|
imageServerActions.error(err);
|
||||||
|
} else {
|
||||||
|
list.map((image, idx) => {
|
||||||
|
let imgSha = image.Id.replace('sha256:', '');
|
||||||
|
if (_.indexOf(this.imagesUsed, imgSha) !== -1) {
|
||||||
|
list[idx].inUse = true;
|
||||||
|
} else {
|
||||||
|
list[idx].inUse = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.localImages = list;
|
||||||
|
imageServerActions.updated(list);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeImage (selectedRepoTag) {
|
||||||
|
this.localImages.some((image) => {
|
||||||
|
image.RepoTags.map(repoTag => {
|
||||||
|
if (repoTag === selectedRepoTag) {
|
||||||
|
this.client.getImage(selectedRepoTag).remove({'force': true}, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
imageServerActions.error(err);
|
||||||
|
} else {
|
||||||
|
imageServerActions.destroyed(data);
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
run (name, repository, tag, local = false) {
|
||||||
tag = tag || 'latest';
|
tag = tag || 'latest';
|
||||||
let imageName = repository + ':' + tag;
|
let imageName = repository + ':' + tag;
|
||||||
|
|
||||||
|
|
@ -200,10 +280,13 @@ export default {
|
||||||
|
|
||||||
this.placeholders[name] = placeholderData;
|
this.placeholders[name] = placeholderData;
|
||||||
localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
|
localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
|
||||||
|
if (local) {
|
||||||
|
this.createContainer(name, {Image: imageName, Tty: true, OpenStdin: true});
|
||||||
|
} else {
|
||||||
this.pullImage(repository, tag, error => {
|
this.pullImage(repository, tag, error => {
|
||||||
if (error) {
|
if (error) {
|
||||||
containerServerActions.error({name, error});
|
containerServerActions.error({name, error});
|
||||||
|
this.refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,6 +307,7 @@ export default {
|
||||||
() => {
|
() => {
|
||||||
containerServerActions.waiting({name, waiting: true});
|
containerServerActions.waiting({name, waiting: true});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateContainer (name, data) {
|
updateContainer (name, data) {
|
||||||
|
|
@ -231,6 +315,7 @@ export default {
|
||||||
existing.inspect((error, existingData) => {
|
existing.inspect((error, existingData) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
containerServerActions.error({name, error});
|
containerServerActions.error({name, error});
|
||||||
|
this.refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -267,6 +352,7 @@ export default {
|
||||||
if (error) {
|
if (error) {
|
||||||
// TODO: handle error
|
// TODO: handle error
|
||||||
containerServerActions.error({newName, error});
|
containerServerActions.error({newName, error});
|
||||||
|
this.refresh();
|
||||||
}
|
}
|
||||||
rimraf(newPath, () => {
|
rimraf(newPath, () => {
|
||||||
if (fs.existsSync(oldPath)) {
|
if (fs.existsSync(oldPath)) {
|
||||||
|
|
@ -288,11 +374,13 @@ export default {
|
||||||
this.client.getContainer(name).stop({t: 5}, stopError => {
|
this.client.getContainer(name).stop({t: 5}, stopError => {
|
||||||
if (stopError && stopError.statusCode !== 304) {
|
if (stopError && stopError.statusCode !== 304) {
|
||||||
containerServerActions.error({name, stopError});
|
containerServerActions.error({name, stopError});
|
||||||
|
this.refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.client.getContainer(name).start(startError => {
|
this.client.getContainer(name).start(startError => {
|
||||||
if (startError && startError.statusCode !== 304) {
|
if (startError && startError.statusCode !== 304) {
|
||||||
containerServerActions.error({name, startError});
|
containerServerActions.error({name, startError});
|
||||||
|
this.refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.fetchContainer(name);
|
this.fetchContainer(name);
|
||||||
|
|
@ -304,6 +392,7 @@ export default {
|
||||||
this.client.getContainer(name).stop({t: 5}, error => {
|
this.client.getContainer(name).stop({t: 5}, error => {
|
||||||
if (error && error.statusCode !== 304) {
|
if (error && error.statusCode !== 304) {
|
||||||
containerServerActions.error({name, error});
|
containerServerActions.error({name, error});
|
||||||
|
this.refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.fetchContainer(name);
|
this.fetchContainer(name);
|
||||||
|
|
@ -314,6 +403,7 @@ export default {
|
||||||
this.client.getContainer(name).start(error => {
|
this.client.getContainer(name).start(error => {
|
||||||
if (error && error.statusCode !== 304) {
|
if (error && error.statusCode !== 304) {
|
||||||
containerServerActions.error({name, error});
|
containerServerActions.error({name, error});
|
||||||
|
this.refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.fetchContainer(name);
|
this.fetchContainer(name);
|
||||||
|
|
@ -325,15 +415,17 @@ export default {
|
||||||
containerServerActions.destroyed({id: name});
|
containerServerActions.destroyed({id: name});
|
||||||
delete this.placeholders[name];
|
delete this.placeholders[name];
|
||||||
localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
|
localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
|
||||||
|
this.refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let container = this.client.getContainer(name);
|
let container = this.client.getContainer(name);
|
||||||
container.unpause(function () {
|
container.unpause( () => {
|
||||||
container.kill(function () {
|
container.kill( () => {
|
||||||
container.remove(function (error) {
|
container.remove( (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
containerServerActions.error({name, error});
|
containerServerActions.error({name, error});
|
||||||
|
this.refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
containerServerActions.destroyed({id: name});
|
containerServerActions.destroyed({id: name});
|
||||||
|
|
@ -341,13 +433,14 @@ export default {
|
||||||
if (fs.existsSync(volumePath)) {
|
if (fs.existsSync(volumePath)) {
|
||||||
rimraf(volumePath, () => {});
|
rimraf(volumePath, () => {});
|
||||||
}
|
}
|
||||||
|
this.refresh();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
active (name) {
|
active (name) {
|
||||||
this.detach();
|
this.detachLog();
|
||||||
this.activeContainerName = name;
|
this.activeContainerName = name;
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
|
|
@ -368,6 +461,8 @@ export default {
|
||||||
timestamps: 1
|
timestamps: 1
|
||||||
}, (err, logStream) => {
|
}, (err, logStream) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
// socket hang up can be captured
|
||||||
|
console.error(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -394,12 +489,12 @@ export default {
|
||||||
timestamps: 1
|
timestamps: 1
|
||||||
}, (err, logStream) => {
|
}, (err, logStream) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
// Socket hang up also can be found here
|
||||||
|
console.error(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.stream) {
|
this.detachLog()
|
||||||
this.detach();
|
|
||||||
}
|
|
||||||
this.stream = logStream;
|
this.stream = logStream;
|
||||||
|
|
||||||
let timeout = null;
|
let timeout = null;
|
||||||
|
|
@ -418,14 +513,22 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
detach () {
|
detachLog() {
|
||||||
if (this.stream) {
|
if (this.stream) {
|
||||||
this.stream.destroy();
|
this.stream.destroy();
|
||||||
this.stream = null;
|
this.stream = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
detachEvent() {
|
||||||
|
if (this.eventStream) {
|
||||||
|
this.eventStream.destroy();
|
||||||
|
this.eventStream = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
listen () {
|
listen () {
|
||||||
|
this.detachEvent()
|
||||||
this.client.getEvents((error, stream) => {
|
this.client.getEvents((error, stream) => {
|
||||||
if (error || !stream) {
|
if (error || !stream) {
|
||||||
// TODO: Add app-wide error handler
|
// TODO: Add app-wide error handler
|
||||||
|
|
@ -433,20 +536,22 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.setEncoding('utf8');
|
stream.setEncoding('utf8');
|
||||||
stream.pipe(JSONStream.parse()).on('data', data => {
|
stream.on('data', json => {
|
||||||
|
let data = JSON.parse(json);
|
||||||
|
|
||||||
if (data.status === 'pull' || data.status === 'untag' || data.status === 'delete' || data.status === 'attach') {
|
if (data.status === 'pull' || data.status === 'untag' || data.status === 'delete' || data.status === 'attach') {
|
||||||
return;
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.status === 'destroy') {
|
if (data.status === 'destroy') {
|
||||||
containerServerActions.destroyed({id: data.id});
|
containerServerActions.destroyed({id: data.id});
|
||||||
this.detach(data.id);
|
this.detachLog()
|
||||||
} else if (data.status === 'kill') {
|
} else if (data.status === 'kill') {
|
||||||
containerServerActions.kill({id: data.id});
|
containerServerActions.kill({id: data.id});
|
||||||
this.detach(data.id);
|
this.detachLog()
|
||||||
} else if (data.status === 'stop') {
|
} else if (data.status === 'stop') {
|
||||||
containerServerActions.stopped({id: data.id});
|
containerServerActions.stopped({id: data.id});
|
||||||
this.detach(data.id);
|
this.detachLog()
|
||||||
} else if (data.status === 'create') {
|
} else if (data.status === 'create') {
|
||||||
this.logs();
|
this.logs();
|
||||||
this.fetchContainer(data.id);
|
this.fetchContainer(data.id);
|
||||||
|
|
@ -457,6 +562,7 @@ export default {
|
||||||
this.fetchContainer(data.id);
|
this.fetchContainer(data.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.eventStream = stream;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -477,6 +583,7 @@ export default {
|
||||||
|
|
||||||
this.client.pull(repository + ':' + tag, opts, (err, stream) => {
|
this.client.pull(repository + ':' + tag, opts, (err, stream) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
console.log('Err: %o', err);
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -573,5 +680,11 @@ export default {
|
||||||
callback(error);
|
callback(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh () {
|
||||||
|
this.fetchAllContainers();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = DockerUtil;
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,14 @@ import machine from './DockerMachineUtil';
|
||||||
import docker from './DockerUtil';
|
import docker from './DockerUtil';
|
||||||
import router from '../router';
|
import router from '../router';
|
||||||
|
|
||||||
|
// Docker Machine exits with 3 to differentiate pre-create check failures (e.g.
|
||||||
|
// virtualization isn't enabled) from normal errors during create (exit code
|
||||||
|
// 1).
|
||||||
|
const precreateCheckExitCode = 3;
|
||||||
|
|
||||||
let _retryPromise = null;
|
let _retryPromise = null;
|
||||||
let _timers = [];
|
let _timers = [];
|
||||||
|
let useNative = util.isNative() ? util.isNative() : true;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
simulateProgress (estimateSeconds) {
|
simulateProgress (estimateSeconds) {
|
||||||
|
|
@ -31,12 +37,20 @@ export default {
|
||||||
_timers = [];
|
_timers = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async useVbox () {
|
||||||
|
metrics.track('Retried Setup with VBox');
|
||||||
|
router.get().transitionTo('loading');
|
||||||
|
setupServerActions.error({ error: { message: null }});
|
||||||
|
_retryPromise.resolve();
|
||||||
|
},
|
||||||
|
|
||||||
retry (removeVM) {
|
retry (removeVM) {
|
||||||
metrics.track('Retried Setup', {
|
metrics.track('Retried Setup', {
|
||||||
removeVM
|
removeVM
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get().transitionTo('loading');
|
router.get().transitionTo('loading');
|
||||||
|
setupServerActions.error({ error: { message: null }});
|
||||||
if (removeVM) {
|
if (removeVM) {
|
||||||
machine.rm().finally(() => {
|
machine.rm().finally(() => {
|
||||||
_retryPromise.resolve();
|
_retryPromise.resolve();
|
||||||
|
|
@ -51,30 +65,43 @@ export default {
|
||||||
return _retryPromise.promise;
|
return _retryPromise.promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
setup() {
|
async setup () {
|
||||||
return util.isLinux() ? this.nativeSetup() : this.nonNativeSetup();
|
while (true) {
|
||||||
|
try {
|
||||||
|
if (util.isNative()) {
|
||||||
|
let stats = fs.statSync('/var/run/docker.sock');
|
||||||
|
if (stats.isSocket()) {
|
||||||
|
await this.nativeSetup();
|
||||||
|
} else {
|
||||||
|
throw new Error('File found is not a socket');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await this.nonNativeSetup();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
metrics.track('Native Setup Failed');
|
||||||
|
setupServerActions.error({error});
|
||||||
|
|
||||||
|
bugsnag.notify('Native Setup Failed', error.message, {
|
||||||
|
'Docker Error': error.message
|
||||||
|
}, 'info');
|
||||||
|
this.clearTimers();
|
||||||
|
await this.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async nativeSetup () {
|
async nativeSetup () {
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
docker.setup('localhost', machine.name());
|
|
||||||
docker.isDockerRunning();
|
|
||||||
|
|
||||||
break;
|
|
||||||
} catch (error) {
|
|
||||||
router.get().transitionTo('setup');
|
router.get().transitionTo('setup');
|
||||||
metrics.track('Native Setup Failed');
|
docker.setup(util.isLinux() ? 'localhost':'docker.local');
|
||||||
setupServerActions.error({error});
|
setupServerActions.started({started: true});
|
||||||
|
this.simulateProgress(20);
|
||||||
let message = error.message.split('\n');
|
return docker.version();
|
||||||
let lastLine = message.length > 1 ? message[message.length - 2] : 'Docker Machine encountered an error.';
|
} catch (error) {
|
||||||
bugsnag.notify('Native Setup Failed', lastLine, {
|
throw new Error(error);
|
||||||
'Docker Machine Logs': error.message
|
|
||||||
}, 'info');
|
|
||||||
|
|
||||||
this.clearTimers();
|
|
||||||
await this.pause();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -86,7 +113,7 @@ export default {
|
||||||
try {
|
try {
|
||||||
setupServerActions.started({started: false});
|
setupServerActions.started({started: false});
|
||||||
|
|
||||||
// Make sure virtulBox and docker-machine are installed
|
// Make sure virtualBox and docker-machine are installed
|
||||||
let virtualBoxInstalled = virtualBox.installed();
|
let virtualBoxInstalled = virtualBox.installed();
|
||||||
let machineInstalled = machine.installed();
|
let machineInstalled = machine.installed();
|
||||||
if (!virtualBoxInstalled || !machineInstalled) {
|
if (!virtualBoxInstalled || !machineInstalled) {
|
||||||
|
|
@ -122,13 +149,16 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
let state = await machine.status();
|
let state = await machine.status();
|
||||||
if (state !== 'Running') {
|
if (state !== 'Running') {
|
||||||
if (state === 'Saved') {
|
|
||||||
router.get().transitionTo('setup');
|
router.get().transitionTo('setup');
|
||||||
|
setupServerActions.started({started: true});
|
||||||
|
if (state === 'Saved') {
|
||||||
this.simulateProgress(10);
|
this.simulateProgress(10);
|
||||||
} else if (state === 'Stopped') {
|
} else if (state === 'Stopped') {
|
||||||
router.get().transitionTo('setup');
|
|
||||||
this.simulateProgress(25);
|
this.simulateProgress(25);
|
||||||
|
} else {
|
||||||
|
this.simulateProgress(40);
|
||||||
}
|
}
|
||||||
|
|
||||||
await machine.start();
|
await machine.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -146,6 +176,7 @@ export default {
|
||||||
|
|
||||||
if (ip) {
|
if (ip) {
|
||||||
docker.setup(ip, machine.name());
|
docker.setup(ip, machine.name());
|
||||||
|
await docker.version();
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Could not determine IP from docker-machine.');
|
throw new Error('Could not determine IP from docker-machine.');
|
||||||
}
|
}
|
||||||
|
|
@ -154,11 +185,17 @@ export default {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
router.get().transitionTo('setup');
|
router.get().transitionTo('setup');
|
||||||
|
|
||||||
let novtx = error.message.indexOf('This computer doesn\'t have VT-X/AMD-v enabled') !== -1;
|
if (error.code === precreateCheckExitCode) {
|
||||||
metrics.track(novtx ? 'Setup Halted' : 'Setup Failed', {
|
metrics.track('Setup Halted', {
|
||||||
virtualBoxVersion,
|
virtualBoxVersion,
|
||||||
machineVersion
|
machineVersion
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
metrics.track('Setup Failed', {
|
||||||
|
virtualBoxVersion,
|
||||||
|
machineVersion
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let message = error.message.split('\n');
|
let message = error.message.split('\n');
|
||||||
let lastLine = message.length > 1 ? message[message.length - 2] : 'Docker Machine encountered an error.';
|
let lastLine = message.length > 1 ? message[message.length - 2] : 'Docker Machine encountered an error.';
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ module.exports = {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
child_process.execFile(args[0], args.slice(1), options, (error, stdout) => {
|
child_process.execFile(args[0], args.slice(1), options, (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(new Error('Encountered an error: ' + error));
|
reject(error);
|
||||||
} else {
|
} else {
|
||||||
resolve(stdout);
|
resolve(stdout);
|
||||||
}
|
}
|
||||||
|
|
@ -37,6 +37,23 @@ module.exports = {
|
||||||
isLinux: function () {
|
isLinux: function () {
|
||||||
return process.platform === 'linux';
|
return process.platform === 'linux';
|
||||||
},
|
},
|
||||||
|
isNative: function () {
|
||||||
|
let native = null;
|
||||||
|
if (native === null) {
|
||||||
|
try {
|
||||||
|
// Check if file exists
|
||||||
|
fs.statSync('/var/run/docker.sock');
|
||||||
|
native = true;
|
||||||
|
} catch (e) {
|
||||||
|
if (this.isLinux()) {
|
||||||
|
native = true;
|
||||||
|
} else {
|
||||||
|
native = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return native;
|
||||||
|
},
|
||||||
binsPath: function () {
|
binsPath: function () {
|
||||||
return this.isWindows() ? path.join(this.home(), 'Kitematic-bins') : path.join('/usr/local/bin');
|
return this.isWindows() ? path.join(this.home(), 'Kitematic-bins') : path.join('/usr/local/bin');
|
||||||
},
|
},
|
||||||
|
|
@ -62,9 +79,6 @@ module.exports = {
|
||||||
// TODO: fix me for windows 7
|
// TODO: fix me for windows 7
|
||||||
return 'Documents';
|
return 'Documents';
|
||||||
},
|
},
|
||||||
supportDir: function () {
|
|
||||||
return app.getPath('userData');
|
|
||||||
},
|
|
||||||
CommandOrCtrl: function () {
|
CommandOrCtrl: function () {
|
||||||
return this.isWindows() ? 'Ctrl' : 'Command';
|
return this.isWindows() ? 'Ctrl' : 'Command';
|
||||||
},
|
},
|
||||||
|
|
@ -95,7 +109,7 @@ module.exports = {
|
||||||
// An official repo is alphanumeric characters separated by dashes or
|
// An official repo is alphanumeric characters separated by dashes or
|
||||||
// underscores.
|
// underscores.
|
||||||
// Examples: myrepo, my-docker-repo, my_docker_repo
|
// Examples: myrepo, my-docker-repo, my_docker_repo
|
||||||
// Non-exapmles: mynamespace/myrepo, my%!repo
|
// Non-examples: mynamespace/myrepo, my%!repo
|
||||||
var repoRegexp = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
|
var repoRegexp = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
|
||||||
return repoRegexp.test(name);
|
return repoRegexp.test(name);
|
||||||
},
|
},
|
||||||
|
|
@ -166,9 +180,9 @@ module.exports = {
|
||||||
dialog.showMessageBox({
|
dialog.showMessageBox({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
buttons: ['OK'],
|
buttons: ['OK'],
|
||||||
message: 'The terminal emulator symbolic link doesn\'t exists. Please read the Wiki at https://github.com/kitematic/kitematic/wiki/Common-Issues-and-Fixes#early-linux-support-from-zedtux.'
|
message: 'The terminal emulator symbolic link doesn\'t exists. Please read the Wiki at https://github.com/docker/kitematic/wiki/Early-Linux-Support.'
|
||||||
});
|
});
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
webPorts: ['80', '8000', '8080', '8888', '3000', '5000', '2368', '9200', '8983']
|
webPorts: ['80', '8000', '8080', '8888', '3000', '5000', '2368', '9200', '8983']
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import metrics from './MetricsUtil';
|
||||||
var WebUtil = {
|
var WebUtil = {
|
||||||
addWindowSizeSaving: function () {
|
addWindowSizeSaving: function () {
|
||||||
window.addEventListener('resize', function () {
|
window.addEventListener('resize', function () {
|
||||||
fs.writeFileSync(path.join(util.supportDir(), 'size'), JSON.stringify({
|
fs.writeFileSync(path.join(app.getPath('userData'), 'size'), JSON.stringify({
|
||||||
width: window.outerWidth,
|
width: window.outerWidth,
|
||||||
height: window.outerHeight
|
height: window.outerHeight
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,10 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 380px;
|
width: 380px;
|
||||||
}
|
}
|
||||||
|
|
@ -160,6 +164,11 @@
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-right: 0.7rem;
|
margin-right: 0.7rem;
|
||||||
}
|
}
|
||||||
|
.results-userimages {
|
||||||
|
border-left: 1px solid @gray-lighter;
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
padding-right: 1.2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -227,6 +236,31 @@
|
||||||
bottom: 1rem;
|
bottom: 1rem;
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
}
|
}
|
||||||
|
.remove {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 auto;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0.8rem 0 0 0;
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: default;
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
.btn-delete {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.small {
|
||||||
|
color: red;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 5px;
|
||||||
|
font-size: 75%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.tag-overlay {
|
.tag-overlay {
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue