mirror of
https://github.com/docker/docs.git
synced 2026-03-27 14:28:47 +07:00
Merge pull request #179 from kitematic/container-store-refactor
Container store refactor
This commit is contained in:
@@ -38,8 +38,8 @@
|
||||
"node_modules/6to5"
|
||||
]
|
||||
},
|
||||
"docker-version": "1.4.1",
|
||||
"boot2docker-version": "1.4.1",
|
||||
"docker-version": "1.5.0",
|
||||
"boot2docker-version": "1.5.0",
|
||||
"atom-shell-version": "0.21.1",
|
||||
"virtualbox-version": "4.3.20",
|
||||
"virtualbox-filename": "VirtualBox-4.3.20.pkg",
|
||||
|
||||
@@ -19,7 +19,7 @@ var ContainerDetail = React.createClass({
|
||||
},
|
||||
init: function () {
|
||||
var currentRoute = _.last(this.getRoutes()).name;
|
||||
if (currentRoute === 'containerDetail') {
|
||||
if (currentRoute === 'containerDetails') {
|
||||
this.transitionTo('containerHome', {name: this.getParams().name});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,640 +0,0 @@
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var Router = require('react-router');
|
||||
var exec = require('exec');
|
||||
var path = require('path');
|
||||
var remote = require('remote');
|
||||
var rimraf = require('rimraf');
|
||||
var fs = require('fs');
|
||||
var dialog = remote.require('dialog');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var ContainerUtil = require('./ContainerUtil');
|
||||
var boot2docker = require('./Boot2Docker');
|
||||
var ContainerDetailsHeader = require('./ContainerDetailsHeader.react');
|
||||
var ContainerHome = require('./ContainerHome.react');
|
||||
var RetinaImage = require('react-retina-image');
|
||||
var Radial = require('./Radial.react');
|
||||
|
||||
var _oldHeight = 0;
|
||||
|
||||
var ContainerDetailsbak = React.createClass({
|
||||
mixins: [Router.State, Router.Navigation],
|
||||
PAGE_HOME: 'home',
|
||||
PAGE_LOGS: 'logs',
|
||||
PAGE_SETTINGS: 'settings',
|
||||
PAGE_PORTS: 'ports',
|
||||
PAGE_VOLUMES: 'volumes',
|
||||
getInitialState: function () {
|
||||
return {
|
||||
logs: [],
|
||||
page: this.PAGE_HOME,
|
||||
env: {},
|
||||
pendingEnv: {},
|
||||
ports: {},
|
||||
volumes: {},
|
||||
defaultPort: null
|
||||
};
|
||||
},
|
||||
componentWillReceiveProps: function () {
|
||||
this.init();
|
||||
},
|
||||
componentDidMount: function () {
|
||||
this.init();
|
||||
ContainerStore.on(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
|
||||
ContainerStore.on(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
ContainerStore.removeListener(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
|
||||
ContainerStore.removeListener(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
},
|
||||
componentDidUpdate: function () {
|
||||
// Scroll logs to bottom
|
||||
var parent = $('.details-logs');
|
||||
if (parent.length) {
|
||||
if (parent.scrollTop() >= _oldHeight) {
|
||||
parent.stop();
|
||||
parent.scrollTop(parent[0].scrollHeight - parent.height());
|
||||
}
|
||||
_oldHeight = parent[0].scrollHeight - parent.height();
|
||||
}
|
||||
},
|
||||
init: function () {
|
||||
var container = ContainerStore.container(this.getParams().name);
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
progress: ContainerStore.progress(this.getParams().name),
|
||||
env: ContainerUtil.env(container),
|
||||
page: this.PAGE_HOME
|
||||
});
|
||||
var ports = ContainerUtil.ports(container);
|
||||
var webPorts = ['80', '8000', '8080', '3000', '5000', '2368'];
|
||||
this.setState({
|
||||
ports: ports,
|
||||
defaultPort: _.find(_.keys(ports), function (port) {
|
||||
return webPorts.indexOf(port) !== -1;
|
||||
})
|
||||
});
|
||||
this.updateLogs();
|
||||
},
|
||||
updateLogs: function (name) {
|
||||
if (name && name !== this.getParams().name) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
logs: ContainerStore.logs(this.getParams().name)
|
||||
});
|
||||
},
|
||||
updateProgress: function (name) {
|
||||
if (name === this.getParams().name) {
|
||||
this.setState({
|
||||
progress: ContainerStore.progress(name)
|
||||
});
|
||||
}
|
||||
},
|
||||
disableRun: function () {
|
||||
return (!this.props.container.State.Running || !this.state.defaultPort);
|
||||
},
|
||||
disableRestart: function () {
|
||||
return (this.props.container.State.Downloading || this.props.container.State.Restarting);
|
||||
},
|
||||
disableTerminal: function () {
|
||||
return (!this.props.container.State.Running);
|
||||
},
|
||||
disableTab: function () {
|
||||
return (this.props.container.State.Downloading);
|
||||
},
|
||||
showHome: function () {
|
||||
if (!this.disableTab()) {
|
||||
/*this.setState({
|
||||
page: this.PAGE_HOME
|
||||
});*/
|
||||
this.transitionTo('containerHome', {name: this.getParams().name});
|
||||
}
|
||||
},
|
||||
showLogs: function () {
|
||||
if (!this.disableTab()) {
|
||||
this.setState({
|
||||
page: this.PAGE_LOGS
|
||||
});
|
||||
}
|
||||
},
|
||||
showPorts: function () {
|
||||
this.setState({
|
||||
page: this.PAGE_PORTS
|
||||
});
|
||||
},
|
||||
showVolumes: function () {
|
||||
this.setState({
|
||||
page: this.PAGE_VOLUMES
|
||||
});
|
||||
},
|
||||
showSettings: function () {
|
||||
if (!this.disableTab()) {
|
||||
this.setState({
|
||||
page: this.PAGE_SETTINGS
|
||||
});
|
||||
}
|
||||
},
|
||||
handleRun: function () {
|
||||
if (this.state.defaultPort && !this.disableRun()) {
|
||||
exec(['open', this.state.ports[this.state.defaultPort].url], function (err) {
|
||||
if (err) { throw err; }
|
||||
});
|
||||
}
|
||||
},
|
||||
handleRestart: function () {
|
||||
if (!this.disableRestart()) {
|
||||
ContainerStore.restart(this.props.container.Name, function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
},
|
||||
handleTerminal: function () {
|
||||
if (!this.disableTerminal()) {
|
||||
var container = this.props.container;
|
||||
var terminal = path.join(process.cwd(), 'resources', 'terminal');
|
||||
var cmd = [terminal, boot2docker.command().replace(/ /g, '\\\\\\\\ ').replace(/\(/g, '\\\\\\\\(').replace(/\)/g, '\\\\\\\\)'), 'ssh', '-t', 'sudo', 'docker', 'exec', '-i', '-t', container.Name, 'sh'];
|
||||
exec(cmd, function (stderr, stdout, code) {
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
if (code) {
|
||||
console.log(stderr);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
handleViewLink: function (url) {
|
||||
exec(['open', url], function (err) {
|
||||
if (err) { throw err; }
|
||||
});
|
||||
},
|
||||
handleChangeDefaultPort: function (port, e) {
|
||||
if (e.target.checked) {
|
||||
this.setState({
|
||||
defaultPort: null
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
defaultPort: port
|
||||
});
|
||||
}
|
||||
},
|
||||
handleChooseVolumeClick: function (dockerVol) {
|
||||
var self = this;
|
||||
dialog.showOpenDialog({properties: ['openDirectory', 'createDirectory']}, function (filenames) {
|
||||
if (!filenames) {
|
||||
return;
|
||||
}
|
||||
var directory = filenames[0];
|
||||
if (directory) {
|
||||
var volumes = _.clone(self.props.container.Volumes);
|
||||
volumes[dockerVol] = directory;
|
||||
var binds = _.pairs(volumes).map(function (pair) {
|
||||
return pair[1] + ':' + pair[0];
|
||||
});
|
||||
ContainerStore.updateContainer(self.props.container.Name, {
|
||||
Binds: binds
|
||||
}, function (err) {
|
||||
if (err) { console.log(err); }
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
handleOpenVolumeClick: function (path) {
|
||||
exec(['open', path], function (err) {
|
||||
if (err) { throw err; }
|
||||
});
|
||||
},
|
||||
handleSaveContainerName: function () {
|
||||
var newName = $('#input-container-name').val();
|
||||
if (newName === this.props.container.Name) {
|
||||
return;
|
||||
}
|
||||
if (fs.existsSync(path.join(process.env.HOME, 'Kitematic', this.props.container.Name))) {
|
||||
fs.renameSync(path.join(process.env.HOME, 'Kitematic', this.props.container.Name), path.join(process.env.HOME, 'Kitematic', newName));
|
||||
}
|
||||
ContainerStore.updateContainer(this.props.container.Name, {
|
||||
name: newName
|
||||
}, function (err) {
|
||||
this.transitionTo('container', {name: newName});
|
||||
if (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
handleSaveEnvVar: function () {
|
||||
var $rows = $('.env-vars .keyval-row');
|
||||
var envVarList = [];
|
||||
$rows.each(function () {
|
||||
var key = $(this).find('.key').val();
|
||||
var val = $(this).find('.val').val();
|
||||
if (!key.length || !val.length) {
|
||||
return;
|
||||
}
|
||||
envVarList.push(key + '=' + val);
|
||||
});
|
||||
var self = this;
|
||||
ContainerStore.updateContainer(self.props.container.Name, {
|
||||
Env: envVarList
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
} else {
|
||||
self.setState({
|
||||
pendingEnv: {}
|
||||
});
|
||||
$('#new-env-key').val('');
|
||||
$('#new-env-val').val('');
|
||||
}
|
||||
});
|
||||
},
|
||||
handleAddPendingEnvVar: function () {
|
||||
var newKey = $('#new-env-key').val();
|
||||
var newVal = $('#new-env-val').val();
|
||||
var newEnv = {};
|
||||
newEnv[newKey] = newVal;
|
||||
this.setState({
|
||||
pendingEnv: _.extend(this.state.pendingEnv, newEnv)
|
||||
});
|
||||
$('#new-env-key').val('');
|
||||
$('#new-env-val').val('');
|
||||
},
|
||||
handleRemoveEnvVar: function (key) {
|
||||
var newEnv = _.omit(this.state.env, key);
|
||||
this.setState({
|
||||
env: newEnv
|
||||
});
|
||||
},
|
||||
handleRemovePendingEnvVar: function (key) {
|
||||
var newEnv = _.omit(this.state.pendingEnv, key);
|
||||
this.setState({
|
||||
pendingEnv: newEnv
|
||||
});
|
||||
},
|
||||
handleDeleteContainer: function () {
|
||||
dialog.showMessageBox({
|
||||
message: 'Are you sure you want to delete this container?',
|
||||
buttons: ['Delete', 'Cancel']
|
||||
}, function (index) {
|
||||
var volumePath = path.join(process.env.HOME, 'Kitematic', this.props.container.Name);
|
||||
if (fs.existsSync(volumePath)) {
|
||||
rimraf(volumePath, function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
if (index === 0) {
|
||||
ContainerStore.remove(this.props.container.Name, function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
handleItemMouseEnterRun: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action .run');
|
||||
$action.css("visibility", "visible");
|
||||
},
|
||||
handleItemMouseLeaveRun: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action .run');
|
||||
$action.css("visibility", "hidden");
|
||||
},
|
||||
handleItemMouseEnterRestart: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action .restart');
|
||||
$action.css("visibility", "visible");
|
||||
},
|
||||
handleItemMouseLeaveRestart: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action .restart');
|
||||
$action.css("visibility", "hidden");
|
||||
},
|
||||
handleItemMouseEnterTerminal: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action .terminal');
|
||||
$action.css("visibility", "visible");
|
||||
},
|
||||
handleItemMouseLeaveTerminal: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action .terminal');
|
||||
$action.css("visibility", "hidden");
|
||||
},
|
||||
render: function () {
|
||||
var self = this;
|
||||
|
||||
if (!this.state) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
var logs = this.state.logs.map(function (l, i) {
|
||||
return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>;
|
||||
});
|
||||
|
||||
if (!this.props.container) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var button;
|
||||
if (this.state.progress === 1) {
|
||||
button = <a className="btn btn-primary" onClick={this.handleClick}>View</a>;
|
||||
} else {
|
||||
button = <a className="btn btn-primary disabled" onClick={this.handleClick}>View</a>;
|
||||
}
|
||||
|
||||
var envVars = _.map(this.state.env, function (val, key) {
|
||||
return (
|
||||
<div key={key} className="keyval-row">
|
||||
<input type="text" className="key line" defaultValue={key}></input>
|
||||
<input type="text" className="val line" defaultValue={val}></input>
|
||||
<a onClick={self.handleRemoveEnvVar.bind(self, key)} className="only-icon btn btn-action small"><span className="icon icon-cross"></span></a>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
var pendingEnvVars = _.map(this.state.pendingEnv, function (val, key) {
|
||||
return (
|
||||
<div key={key} className="keyval-row">
|
||||
<input type="text" className="key line" defaultValue={key}></input>
|
||||
<input type="text" className="val line" defaultValue={val}></input>
|
||||
<a onClick={self.handleRemovePendingEnvVar.bind(self, key)} className="only-icon btn btn-action small"><span className="icon icon-arrow-undo"></span></a>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
var disabledClass = '';
|
||||
if (!this.props.container.State.Running) {
|
||||
disabledClass = 'disabled';
|
||||
}
|
||||
|
||||
/*var buttonClass = React.addons.classSet({
|
||||
btn: true,
|
||||
'btn-action': true,
|
||||
'with-icon': true,
|
||||
disabled: !this.props.container.State.Running
|
||||
});
|
||||
|
||||
var restartButtonClass = React.addons.classSet({
|
||||
btn: true,
|
||||
'btn-action': true,
|
||||
'with-icon': true,
|
||||
disabled: this.props.container.State.Downloading || this.props.container.State.Restarting
|
||||
});
|
||||
|
||||
var viewButtonClass = React.addons.classSet({
|
||||
btn: true,
|
||||
'btn-action': true,
|
||||
'with-icon': true,
|
||||
disabled: !this.props.container.State.Running || !this.state.defaultPort
|
||||
});
|
||||
|
||||
var kitematicVolumes = _.pairs(this.props.container.Volumes).filter(function (pair) {
|
||||
return pair[1].indexOf(path.join(process.env.HOME, 'Kitematic')) !== -1;
|
||||
});
|
||||
|
||||
var volumesButtonClass = React.addons.classSet({
|
||||
btn: true,
|
||||
'btn-action': true,
|
||||
'with-icon': true,
|
||||
disabled: !kitematicVolumes.length
|
||||
});
|
||||
|
||||
var textButtonClasses = React.addons.classSet({
|
||||
'btn': true,
|
||||
'btn-action': true,
|
||||
'only-icon': true,
|
||||
'active': this.state.page === this.PAGE_LOGS,
|
||||
disabled: this.props.container.State.Downloading
|
||||
});
|
||||
|
||||
var gearButtonClass = React.addons.classSet({
|
||||
'btn': true,
|
||||
'btn-action': true,
|
||||
'only-icon': true,
|
||||
'active': this.state.page === this.PAGE_SETTINGS,
|
||||
disabled: this.props.container.State.Downloading
|
||||
});*/
|
||||
|
||||
var ports = _.map(_.pairs(self.state.ports), function (pair) {
|
||||
var key = pair[0];
|
||||
var val = pair[1];
|
||||
return (
|
||||
<div key={key} className="table-values">
|
||||
<span className="value-left">{key}</span><span className="icon icon-arrow-right"></span>
|
||||
<a className="value-right" onClick={self.handleViewLink.bind(self, val.url)}>{val.display}</a>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
var volumes = _.map(self.props.container.Volumes, function (val, key) {
|
||||
if (!val || val.indexOf(process.env.HOME) === -1) {
|
||||
val = 'No Host Folder';
|
||||
}
|
||||
return (
|
||||
<div key={key} className="table-values">
|
||||
<span className="value-left">{key}</span><span className="icon icon-arrow-right"></span>
|
||||
<a className="value-right">{val.replace(process.env.HOME, '~')}</a>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
var body;
|
||||
if (this.props.container.State.Downloading) {
|
||||
if (this.state.progress) {
|
||||
body = (
|
||||
<div className="details-progress">
|
||||
<h2>Downloading Image</h2>
|
||||
<Radial progress={Math.round(this.state.progress * 100)}/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
body = (
|
||||
<div className="details-progress">
|
||||
<h2>Connecting to Docker Hub</h2>
|
||||
<Radial spin="true" progress="90"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (this.state.page === this.PAGE_HOME) {
|
||||
body = (
|
||||
<ContainerHome ports={this.state.ports} defaultPort={this.state.defaultPort} logs={logs} container={this.props.container} />
|
||||
);
|
||||
} else if (this.state.page === this.PAGE_LOGS) {
|
||||
body = (
|
||||
<div className="details-panel details-logs logs">
|
||||
{logs}
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.page === this.PAGE_PORTS) {
|
||||
body = (
|
||||
<div className="details-panel">
|
||||
<div className="ports">
|
||||
<h3>Configure Ports</h3>
|
||||
<div className="table">
|
||||
<div className="table-labels">
|
||||
<div className="label-left">DOCKER PORT</div>
|
||||
<div className="label-right">MAC PORT</div>
|
||||
</div>
|
||||
{ports}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.page === this.PAGE_VOLUMES) {
|
||||
body = (
|
||||
<div className="details-panel">
|
||||
<div className="volumes">
|
||||
<h3>Configure Volumes</h3>
|
||||
<div className="table">
|
||||
<div className="table-labels">
|
||||
<div className="label-left">DOCKER FOLDER</div>
|
||||
<div className="label-right">MAC FOLDER</div>
|
||||
</div>
|
||||
{volumes}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
var rename = (
|
||||
<div className="settings-section">
|
||||
<h3>Container Name</h3>
|
||||
<div className="container-name">
|
||||
<input id="input-container-name" type="text" className="line" placeholder="Container Name" defaultValue={this.props.container.Name}></input>
|
||||
</div>
|
||||
<a className="btn btn-action" onClick={this.handleSaveContainerName}>Save</a>
|
||||
</div>
|
||||
);
|
||||
body = (
|
||||
<div className="details-panel">
|
||||
<div className="settings">
|
||||
{rename}
|
||||
<div className="settings-section">
|
||||
<h3>Environment Variables</h3>
|
||||
<div className="env-vars-labels">
|
||||
<div className="label-key">KEY</div>
|
||||
<div className="label-val">VALUE</div>
|
||||
</div>
|
||||
<div className="env-vars">
|
||||
{envVars}
|
||||
{pendingEnvVars}
|
||||
<div className="keyval-row">
|
||||
<input id="new-env-key" type="text" className="key line"></input>
|
||||
<input id="new-env-val" type="text" className="val line"></input>
|
||||
<a onClick={this.handleAddPendingEnvVar} className="only-icon btn btn-positive small"><span className="icon icon-add-1"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
<a className="btn btn-action" onClick={this.handleSaveEnvVar}>Save</a>
|
||||
</div>
|
||||
<div className="settings-section">
|
||||
<h3>Delete Container</h3>
|
||||
<a className="btn btn-action" onClick={this.handleDeleteContainer}>Delete Container</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var tabHomeClasses = React.addons.classSet({
|
||||
'tab': true,
|
||||
'active': this.state.page === this.PAGE_HOME,
|
||||
disabled: this.disableTab()
|
||||
});
|
||||
|
||||
var tabLogsClasses = React.addons.classSet({
|
||||
'tab': true,
|
||||
'active': this.state.page === this.PAGE_LOGS,
|
||||
disabled: this.disableTab()
|
||||
});
|
||||
|
||||
var tabSettingsClasses = React.addons.classSet({
|
||||
'tab': true,
|
||||
'active': this.state.page === this.PAGE_SETTINGS,
|
||||
disabled: this.disableTab()
|
||||
});
|
||||
|
||||
/*var ports = _.map(_.pairs(self.state.ports), function (pair, index, list) {
|
||||
var key = pair[0];
|
||||
var val = pair[1];
|
||||
return (
|
||||
<div key={key} className="table-values">
|
||||
<span className="value-left">{key}</span><span className="icon icon-arrow-right"></span>
|
||||
<a className="value-right" onClick={self.handleViewLink.bind(self, val.url)}>{val.display}</a>
|
||||
<input onChange={self.handleChangeDefaultPort.bind(self, key)} type="checkbox" checked={self.state.defaultPort === key}/> <label>Default</label>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
var volumes = _.map(self.props.container.Volumes, function (val, key) {
|
||||
if (!val || val.indexOf(process.env.HOME) === -1) {
|
||||
val = <span>No folder<a className="btn btn-primary btn-xs" onClick={self.handleChooseVolumeClick.bind(self, key)}>Choose</a></span>;
|
||||
} else {
|
||||
val = <span><a className="value-right" onClick={self.handleOpenVolumeClick.bind(self, val)}>{val.replace(process.env.HOME, '~')}</a> <a className="btn btn-primary btn-xs" onClick={self.handleChooseVolumeClick.bind(self, key)}>Choose</a></span>;
|
||||
}
|
||||
return (
|
||||
<div key={key} className="table-values">
|
||||
<span className="value-left">{key}</span><span className="icon icon-arrow-right"></span>
|
||||
{val}
|
||||
</div>
|
||||
);
|
||||
});*/
|
||||
|
||||
/* var view;
|
||||
if (this.state.defaultPort) {
|
||||
view = (
|
||||
<div className="action btn-group">
|
||||
<a className={viewButtonClass} onClick={this.handleView}><span className="icon icon-preview-2"></span><span className="content">View</span></a>
|
||||
<a className={dropdownViewButtonClass} onClick={this.handleViewDropdown}><span className="icon-dropdown icon icon-arrow-37"></span></a>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
view = (
|
||||
<div className="action">
|
||||
<a className={dropdownViewButtonClass} onClick={this.handleViewDropdown}><span className="icon icon-preview-2"></span> <span className="content">Ports</span> <span className="icon-dropdown icon icon-arrow-37"></span></a>
|
||||
</div>
|
||||
);
|
||||
}*/
|
||||
|
||||
var runActionClass = React.addons.classSet({
|
||||
action: true,
|
||||
disabled: this.disableRun()
|
||||
});
|
||||
|
||||
var restartActionClass = React.addons.classSet({
|
||||
action: true,
|
||||
disabled: this.disableRestart()
|
||||
});
|
||||
|
||||
var terminalActionClass = React.addons.classSet({
|
||||
action: true,
|
||||
disabled: this.disableTerminal()
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="details">
|
||||
<ContainerDetailsHeader container={this.props.container} />
|
||||
<div className="details-subheader">
|
||||
<div className="details-header-actions">
|
||||
<div className={runActionClass} onMouseEnter={this.handleItemMouseEnterRun} onMouseLeave={this.handleItemMouseLeaveRun}>
|
||||
<span className="action-icon" onClick={this.handleRun}><RetinaImage src="button-run.png"/></span>
|
||||
<span className="btn-label run">Run</span>
|
||||
</div>
|
||||
<div className={restartActionClass} onMouseEnter={this.handleItemMouseEnterRestart} onMouseLeave={this.handleItemMouseLeaveRestart}>
|
||||
<span className="action-icon" onClick={this.handleRestart}><RetinaImage src="button-restart.png"/></span>
|
||||
<span className="btn-label restart">Restart</span>
|
||||
</div>
|
||||
<div className={terminalActionClass} onMouseEnter={this.handleItemMouseEnterTerminal} onMouseLeave={this.handleItemMouseLeaveTerminal}>
|
||||
<span className="action-icon" onClick={this.handleTerminal}><RetinaImage src="button-terminal.png"/></span>
|
||||
<span className="btn-label terminal">Terminal</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="details-subheader-tabs">
|
||||
<span className={tabHomeClasses} onClick={this.showHome}>Home</span>
|
||||
<span className={tabLogsClasses} onClick={this.showLogs}>Logs</span>
|
||||
<span className={tabSettingsClasses} onClick={this.showSettings}>Settings</span>
|
||||
</div>
|
||||
</div>
|
||||
{body}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ContainerDetailsbak;
|
||||
@@ -90,7 +90,7 @@ var ContainerHome = React.createClass({
|
||||
<ContainerHomePreview />
|
||||
</div>
|
||||
<div className="right">
|
||||
<ContainerHomeLogs />
|
||||
<ContainerHomeLogs/>
|
||||
<ContainerHomeFolders container={this.props.container} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,7 +116,7 @@ var ContainerHome = React.createClass({
|
||||
<div className="details-panel home">
|
||||
<div className="content">
|
||||
<div className="left">
|
||||
<ContainerHomeLogs />
|
||||
<ContainerHomeLogs/>
|
||||
</div>
|
||||
{right}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var LogStore = require('./LogStore');
|
||||
var Router = require('react-router');
|
||||
|
||||
var ContainerHomeLogs = React.createClass({
|
||||
@@ -15,10 +15,10 @@ var ContainerHomeLogs = React.createClass({
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.init();
|
||||
ContainerStore.on(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
LogStore.on(LogStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
ContainerStore.removeListener(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
LogStore.removeListener(LogStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
},
|
||||
componentDidUpdate: function () {
|
||||
// Scroll logs to bottom
|
||||
@@ -39,7 +39,7 @@ var ContainerHomeLogs = React.createClass({
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
logs: ContainerStore.logs(this.getParams().name)
|
||||
logs: LogStore.logs(this.getParams().name)
|
||||
});
|
||||
},
|
||||
handleClickLogs: function () {
|
||||
|
||||
@@ -30,19 +30,7 @@ var ContainerListItem = React.createClass({
|
||||
render: function () {
|
||||
var self = this;
|
||||
var container = this.props.container;
|
||||
var downloadingImage = null, downloading = false;
|
||||
var env = container.Config.Env;
|
||||
if (env.length) {
|
||||
var obj = _.object(env.map(function (e) {
|
||||
return e.split('=');
|
||||
}));
|
||||
if (obj.KITEMATIC_DOWNLOADING) {
|
||||
downloading = true;
|
||||
}
|
||||
downloadingImage = obj.KITEMATIC_DOWNLOADING_IMAGE || null;
|
||||
}
|
||||
|
||||
var imageName = downloadingImage || container.Config.Image;
|
||||
var imageName = container.Config.Image;
|
||||
|
||||
// Synchronize all animations
|
||||
var style = {
|
||||
@@ -50,7 +38,7 @@ var ContainerListItem = React.createClass({
|
||||
};
|
||||
|
||||
var state;
|
||||
if (downloading) {
|
||||
if (container.State.Downloading) {
|
||||
state = <div className="state state-downloading"><div style={style} className="downloading-arrow"></div></div>;
|
||||
} else if (container.State.Running && !container.State.Paused) {
|
||||
state = <div className="state state-running"><div style={style} className="runningwave"></div></div>;
|
||||
@@ -66,7 +54,7 @@ var ContainerListItem = React.createClass({
|
||||
}
|
||||
|
||||
return (
|
||||
<Router.Link data-container={name} to="containerDetail" params={{name: container.Name}}>
|
||||
<Router.Link data-container={name} to="containerDetails" params={{name: container.Name}}>
|
||||
<li onMouseEnter={self.handleItemMouseEnter} onMouseLeave={self.handleItemMouseLeave}>
|
||||
{state}
|
||||
<div className="info">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var LogStore = require('./LogStore');
|
||||
var Router = require('react-router');
|
||||
|
||||
var ContainerLogs = React.createClass({
|
||||
@@ -15,10 +15,10 @@ var ContainerLogs = React.createClass({
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.init();
|
||||
ContainerStore.on(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
LogStore.on(LogStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
ContainerStore.removeListener(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
LogStore.removeListener(LogStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
},
|
||||
componentDidUpdate: function () {
|
||||
// Scroll logs to bottom
|
||||
@@ -39,7 +39,7 @@ var ContainerLogs = React.createClass({
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
logs: ContainerStore.logs(this.getParams().name)
|
||||
logs: LogStore.logs(this.getParams().name)
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
|
||||
@@ -1,48 +1,20 @@
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var async = require('async');
|
||||
var path = require('path');
|
||||
var assign = require('object-assign');
|
||||
var Convert = require('ansi-to-html');
|
||||
var docker = require('./Docker');
|
||||
var registry = require('./Registry');
|
||||
var ContainerUtil = require('./ContainerUtil');
|
||||
|
||||
var convert = new Convert();
|
||||
var _recommended = [];
|
||||
var _placeholders = {};
|
||||
var _containers = {};
|
||||
var _progress = {};
|
||||
var _logs = {};
|
||||
var _streams = {};
|
||||
var _muted = {};
|
||||
|
||||
var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
CLIENT_CONTAINER_EVENT: 'client_container_event',
|
||||
CLIENT_RECOMMENDED_EVENT: 'client_recommended_event',
|
||||
SERVER_CONTAINER_EVENT: 'server_container_event',
|
||||
SERVER_PROGRESS_EVENT: 'server_progress_event',
|
||||
SERVER_LOGS_EVENT: 'server_logs_event',
|
||||
_pullScratchImage: function (callback) {
|
||||
var image = docker.client().getImage('scratch:latest');
|
||||
image.inspect(function (err, data) {
|
||||
if (!data) {
|
||||
docker.client().pull('scratch:latest', function (err, stream) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', function () {});
|
||||
stream.on('end', function () {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
_pullImage: function (repository, tag, callback, progressCallback) {
|
||||
registry.layers(repository, tag, function (err, layerSizes) {
|
||||
|
||||
@@ -89,7 +61,7 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
|
||||
var totalReceived = chunks.reduce(function (pv, sv) {
|
||||
return pv + sv;
|
||||
});
|
||||
}, 0);
|
||||
|
||||
var totalProgress = totalReceived / totalBytes;
|
||||
progressCallback(totalProgress);
|
||||
@@ -101,12 +73,6 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
});
|
||||
});
|
||||
},
|
||||
_escapeHTML: function (html) {
|
||||
var text = document.createTextNode(html);
|
||||
var div = document.createElement('div');
|
||||
div.appendChild(text);
|
||||
return div.innerHTML;
|
||||
},
|
||||
_createContainer: function (name, containerData, callback) {
|
||||
var existing = docker.client().getContainer(name);
|
||||
var self = this;
|
||||
@@ -118,14 +84,8 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
if (containerData.Config && containerData.Config.Image) {
|
||||
containerData.Image = containerData.Config.Image;
|
||||
}
|
||||
existing.kill(function (err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
existing.remove(function (err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
existing.kill(function () {
|
||||
existing.remove(function () {
|
||||
docker.client().getImage(containerData.Image).inspect(function (err, data) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
@@ -161,31 +121,6 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
});
|
||||
});
|
||||
},
|
||||
_createPlaceholderContainer: function (imageName, name, callback) {
|
||||
var self = this;
|
||||
this._pullScratchImage(function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
docker.client().createContainer({
|
||||
Image: 'scratch:latest',
|
||||
Tty: false,
|
||||
Env: [
|
||||
'KITEMATIC_DOWNLOADING=true',
|
||||
'KITEMATIC_DOWNLOADING_IMAGE=' + imageName
|
||||
],
|
||||
Cmd: 'placeholder',
|
||||
name: name
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
self.fetchContainer(name, callback);
|
||||
});
|
||||
});
|
||||
},
|
||||
_generateName: function (repository) {
|
||||
var base = _.last(repository.split('/'));
|
||||
var count = 1;
|
||||
@@ -201,18 +136,22 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
}
|
||||
},
|
||||
_resumePulling: function () {
|
||||
var downloading = _.filter(_.values(_containers), function (container) {
|
||||
var downloading = _.filter(_.values(this.containers()), function (container) {
|
||||
return container.State.Downloading;
|
||||
});
|
||||
|
||||
// Recover any pulls that were happening
|
||||
var self = this;
|
||||
downloading.forEach(function (container) {
|
||||
docker.client().pull(container.KitematicDownloadingImage, function (err, stream) {
|
||||
docker.client().pull(container.Config.Image, function (err, stream) {
|
||||
delete _placeholders[container.Name];
|
||||
localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', function () {});
|
||||
stream.on('end', function () {
|
||||
self._createContainer(container.Name, {Image: container.KitematicDownloadingImage}, function () {});
|
||||
self._createContainer(container.Name, {Image: container.Config.Image}, function () {
|
||||
self.emit(self.CLIENT_CONTAINER_EVENT, container.Name);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -262,13 +201,18 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
var placeholderData = JSON.parse(localStorage.getItem('store.placeholders'));
|
||||
console.log(placeholderData);
|
||||
console.log(_.keys(_containers));
|
||||
if (placeholderData) {
|
||||
_placeholders = _.omit(placeholderData, _.keys(_containers));
|
||||
localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
|
||||
}
|
||||
console.log(_placeholders);
|
||||
this.emit(this.CLIENT_CONTAINER_EVENT);
|
||||
this._resumePulling();
|
||||
this._startListeningToEvents();
|
||||
}.bind(this));
|
||||
this.fetchRecommended(function () {
|
||||
this.emit(this.CLIENT_RECOMMENDED_EVENT);
|
||||
}.bind(this));
|
||||
},
|
||||
fetchContainer: function (id, callback) {
|
||||
docker.client().getContainer(id).inspect(function (err, container) {
|
||||
@@ -281,15 +225,6 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
}
|
||||
// Fix leading slash in container names
|
||||
container.Name = container.Name.replace('/', '');
|
||||
|
||||
// Add Downloading State (stored in environment variables) to containers for Kitematic
|
||||
var env = ContainerUtil.env(container);
|
||||
container.State.Downloading = !!env.KITEMATIC_DOWNLOADING;
|
||||
container.KitematicDownloadingImage = env.KITEMATIC_DOWNLOADING_IMAGE;
|
||||
|
||||
this.fetchLogs(container.Name, function () {
|
||||
}.bind(this));
|
||||
|
||||
_containers[container.Name] = container;
|
||||
callback(null, container);
|
||||
}
|
||||
@@ -311,103 +246,42 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
});
|
||||
});
|
||||
},
|
||||
fetchRecommended: function (callback) {
|
||||
if (_recommended.length) {
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
url: 'https://kitematic.com/recommended.json',
|
||||
cache: false,
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
var recommended = res.repos;
|
||||
async.map(recommended, function (rec, callback) {
|
||||
$.get('https://registry.hub.docker.com/v1/search?q=' + rec.repo, function (data) {
|
||||
var results = data.results;
|
||||
var result = _.find(results, function (r) {
|
||||
return r.name === rec.repo;
|
||||
});
|
||||
callback(null, _.extend(result, rec));
|
||||
});
|
||||
}, function (err, results) {
|
||||
_recommended = results.filter(function(r) { return !!r; });
|
||||
callback();
|
||||
});
|
||||
},
|
||||
error: function (err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
fetchLogs: function (name, callback) {
|
||||
var index = 0;
|
||||
var self = this;
|
||||
docker.client().getContainer(name).logs({
|
||||
follow: true,
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
timestamps: true
|
||||
}, function (err, stream) {
|
||||
callback(err);
|
||||
if (_streams[name]) {
|
||||
return;
|
||||
}
|
||||
_streams[name] = stream;
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
_logs[name] = [];
|
||||
stream.setEncoding('utf8');
|
||||
var timeout;
|
||||
stream.on('data', function (buf) {
|
||||
// Every other message is a header
|
||||
if (index % 2 === 1) {
|
||||
//var time = buf.substr(0,buf.indexOf(' '));
|
||||
var msg = buf.substr(buf.indexOf(' ')+1);
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
timeout = setTimeout(function () {
|
||||
timeout = null;
|
||||
self.emit(self.SERVER_LOGS_EVENT, name);
|
||||
}, 100);
|
||||
_logs[name].push(convert.toHtml(self._escapeHTML(msg)));
|
||||
}
|
||||
index += 1;
|
||||
});
|
||||
stream.on('end', function () {
|
||||
delete _streams[name];
|
||||
});
|
||||
});
|
||||
},
|
||||
create: function (repository, tag, callback) {
|
||||
tag = tag || 'latest';
|
||||
var self = this;
|
||||
var imageName = repository + ':' + tag;
|
||||
var containerName = this._generateName(repository);
|
||||
// Pull image
|
||||
self._createPlaceholderContainer(imageName, containerName, function (err, container) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
|
||||
_placeholders[containerName] = {
|
||||
Name: containerName,
|
||||
Image: imageName,
|
||||
Config: {
|
||||
Image: imageName,
|
||||
},
|
||||
State: {
|
||||
Downloading: true
|
||||
}
|
||||
_containers[containerName] = container;
|
||||
self.emit(self.CLIENT_CONTAINER_EVENT, containerName, 'create');
|
||||
_muted[containerName] = true;
|
||||
_progress[containerName] = 0;
|
||||
self._pullImage(repository, tag, function () {
|
||||
self._createContainer(containerName, {Image: imageName}, function () {
|
||||
delete _progress[containerName];
|
||||
_muted[containerName] = false;
|
||||
self.emit(self.CLIENT_CONTAINER_EVENT, containerName);
|
||||
});
|
||||
}, function (progress) {
|
||||
_progress[containerName] = progress;
|
||||
self.emit(self.SERVER_PROGRESS_EVENT, containerName);
|
||||
};
|
||||
console.log(_placeholders);
|
||||
console.log(JSON.stringify(_placeholders));
|
||||
localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
|
||||
self.emit(self.CLIENT_CONTAINER_EVENT, containerName, 'create');
|
||||
|
||||
_muted[containerName] = true;
|
||||
_progress[containerName] = 0;
|
||||
self._pullImage(repository, tag, function () {
|
||||
delete _placeholders[containerName];
|
||||
localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
|
||||
self._createContainer(containerName, {Image: imageName}, function () {
|
||||
delete _progress[containerName];
|
||||
_muted[containerName] = false;
|
||||
self.emit(self.CLIENT_CONTAINER_EVENT, containerName);
|
||||
});
|
||||
callback(null, containerName);
|
||||
}, function (progress) {
|
||||
_progress[containerName] = progress;
|
||||
self.emit(self.SERVER_PROGRESS_EVENT, containerName);
|
||||
});
|
||||
callback(null, containerName);
|
||||
},
|
||||
updateContainer: function (name, data, callback) {
|
||||
_muted[name] = true;
|
||||
@@ -428,6 +302,10 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
});
|
||||
},
|
||||
remove: function (name, callback) {
|
||||
if (_placeholders[name]) {
|
||||
delete _placeholders[name];
|
||||
return;
|
||||
}
|
||||
var container = docker.client().getContainer(name);
|
||||
if (_containers[name].State.Paused) {
|
||||
container.unpause(function (err) {
|
||||
@@ -467,25 +345,19 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
}
|
||||
},
|
||||
containers: function() {
|
||||
return _containers;
|
||||
return _.extend(_containers, _placeholders);
|
||||
},
|
||||
container: function (name) {
|
||||
return _containers[name];
|
||||
return this.containers()[name];
|
||||
},
|
||||
sorted: function () {
|
||||
return _.values(_containers).sort(function (a, b) {
|
||||
return _.values(this.containers()).sort(function (a, b) {
|
||||
return a.Name.localeCompare(b.Name);
|
||||
});
|
||||
},
|
||||
recommended: function () {
|
||||
return _recommended;
|
||||
},
|
||||
progress: function (name) {
|
||||
return _progress[name];
|
||||
},
|
||||
logs: function (name) {
|
||||
return _logs[name] || [];
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ContainerStore;
|
||||
|
||||
@@ -13,6 +13,9 @@ var ContainerUtil = {
|
||||
}));
|
||||
},
|
||||
ports: function (container) {
|
||||
if (!container.NetworkSettings) {
|
||||
return {};
|
||||
}
|
||||
var res = {};
|
||||
var ip = docker.host;
|
||||
_.each(container.NetworkSettings.Ports, function (value, key) {
|
||||
|
||||
70
src/LogStore.js
Normal file
70
src/LogStore.js
Normal file
@@ -0,0 +1,70 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var assign = require('object-assign');
|
||||
var Convert = require('ansi-to-html');
|
||||
var docker = require('./Docker');
|
||||
|
||||
var _convert = new Convert();
|
||||
var _logs = {};
|
||||
var _streams = {};
|
||||
|
||||
var LogStore = assign(Object.create(EventEmitter.prototype), {
|
||||
SERVER_LOGS_EVENT: 'server_logs_event',
|
||||
_escapeHTML: function (html) {
|
||||
var text = document.createTextNode(html);
|
||||
var div = document.createElement('div');
|
||||
div.appendChild(text);
|
||||
return div.innerHTML;
|
||||
},
|
||||
fetchLogs: function (name) {
|
||||
if (!name || !docker.client()) {
|
||||
return;
|
||||
}
|
||||
var index = 0;
|
||||
var self = this;
|
||||
docker.client().getContainer(name).logs({
|
||||
follow: true,
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
timestamps: true
|
||||
}, function (err, stream) {
|
||||
if (_streams[name]) {
|
||||
return;
|
||||
}
|
||||
_streams[name] = stream;
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
_logs[name] = [];
|
||||
stream.setEncoding('utf8');
|
||||
var timeout;
|
||||
stream.on('data', function (buf) {
|
||||
// Every other message is a header
|
||||
if (index % 2 === 1) {
|
||||
//var time = buf.substr(0,buf.indexOf(' '));
|
||||
var msg = buf.substr(buf.indexOf(' ')+1);
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
timeout = setTimeout(function () {
|
||||
timeout = null;
|
||||
self.emit(self.SERVER_LOGS_EVENT, name);
|
||||
}, 100);
|
||||
_logs[name].push(_convert.toHtml(self._escapeHTML(msg)));
|
||||
}
|
||||
index += 1;
|
||||
});
|
||||
stream.on('end', function () {
|
||||
delete _streams[name];
|
||||
});
|
||||
});
|
||||
},
|
||||
logs: function (name) {
|
||||
if (!_streams[name]) {
|
||||
this.fetchLogs(name);
|
||||
}
|
||||
return _logs[name] || [];
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = LogStore;
|
||||
32
src/Main.js
32
src/Main.js
@@ -34,26 +34,14 @@ bugsnag.notifyReleaseStages = ['production'];
|
||||
bugsnag.appVersion = app.getVersion();
|
||||
|
||||
router.run(Handler => React.render(<Handler/>, document.body));
|
||||
if (!window.location.hash.length || window.location.hash === '#/') {
|
||||
SetupStore.run().then(boot2docker.ip).then(ip => {
|
||||
console.log(ip);
|
||||
docker.setHost(ip);
|
||||
ContainerStore.init(function (err) {
|
||||
if (err) { console.log(err); }
|
||||
router.transitionTo('containers');
|
||||
});
|
||||
}).catch(err => {
|
||||
bugsnag.notify(err);
|
||||
SetupStore.run().then(boot2docker.ip).then(ip => {
|
||||
console.log(ip);
|
||||
docker.setHost(ip);
|
||||
ContainerStore.init(function (err) {
|
||||
if (err) { console.log(err); }
|
||||
router.transitionTo('containers');
|
||||
});
|
||||
} else {
|
||||
console.log('Skipping installer.');
|
||||
router.transitionTo('containers');
|
||||
boot2docker.ip().then(ip => {
|
||||
docker.setHost(ip);
|
||||
ContainerStore.init(function (err) {
|
||||
if (err) { console.log(err); }
|
||||
});
|
||||
}).catch(err => {
|
||||
bugsnag.notify(err);
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
bugsnag.notify(err);
|
||||
});
|
||||
|
||||
@@ -5,13 +5,16 @@ var RetinaImage = require('react-retina-image');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var Radial = require('./Radial.react');
|
||||
var assign = require('object-assign');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
var _recommended = [];
|
||||
|
||||
var NewContainer = React.createClass({
|
||||
_searchRequest: null,
|
||||
getInitialState: function () {
|
||||
return {
|
||||
query: '',
|
||||
results: [],
|
||||
results: _recommended,
|
||||
loading: false,
|
||||
tags: {},
|
||||
active: null,
|
||||
@@ -23,14 +26,8 @@ var NewContainer = React.createClass({
|
||||
creating: []
|
||||
});
|
||||
this.refs.searchInput.getDOMNode().focus();
|
||||
ContainerStore.on(ContainerStore.CLIENT_RECOMMENDED_EVENT, this.update);
|
||||
this.update();
|
||||
},
|
||||
update: function () {
|
||||
if (!this.state.query.length) {
|
||||
this.setState({
|
||||
results: ContainerStore.recommended()
|
||||
});
|
||||
if (!_recommended.length) {
|
||||
this.recommended();
|
||||
}
|
||||
},
|
||||
search: function (query) {
|
||||
@@ -59,6 +56,40 @@ var NewContainer = React.createClass({
|
||||
}
|
||||
});
|
||||
},
|
||||
recommended: function () {
|
||||
if (this._searchRequest) {
|
||||
this._searchRequest.abort();
|
||||
this._searchRequest = null;
|
||||
}
|
||||
|
||||
if (_recommended.length) {
|
||||
return;
|
||||
}
|
||||
Promise.resolve($.ajax({
|
||||
url: 'https://kitematic.com/recommended.json',
|
||||
cache: false,
|
||||
dataType: 'json',
|
||||
})).then(res => res.repos).map(repo => {
|
||||
return $.get('https://registry.hub.docker.com/v1/search?q=' + repo.repo).then(data => {
|
||||
var results = data.results;
|
||||
var result = _.find(results, function (r) {
|
||||
return r.name === repo.repo;
|
||||
});
|
||||
return _.extend(result, repo);
|
||||
});
|
||||
}).then(results => {
|
||||
_recommended = results.filter(r => !!r);
|
||||
if (!this.state.query.length) {
|
||||
if (this.isMounted()) {
|
||||
this.setState({
|
||||
results: _recommended
|
||||
});
|
||||
}
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
},
|
||||
handleChange: function (e) {
|
||||
var query = e.target.value;
|
||||
|
||||
@@ -70,7 +101,7 @@ var NewContainer = React.createClass({
|
||||
if (!query.length) {
|
||||
this.setState({
|
||||
query: query,
|
||||
results: ContainerStore.recommended()
|
||||
results: _recommended
|
||||
});
|
||||
} else {
|
||||
var self = this;
|
||||
|
||||
@@ -27,7 +27,7 @@ var App = React.createClass({
|
||||
var routes = (
|
||||
<Route name="app" path="/" handler={App}>
|
||||
<Route name="containers" handler={Containers}>
|
||||
<Route name="containerDetail" path="/containers/:name" handler={ContainerDetails}>
|
||||
<Route name="containerDetails" path="/containers/:name" handler={ContainerDetails}>
|
||||
<Route name="containerHome" path="/containers/:name/home" handler={ContainerHome} />
|
||||
<Route name="containerLogs" path="/containers/:name/logs" handler={ContainerLogs}/>
|
||||
<Route name="containerSettings" path="/containers/:name/settings" handler={ContainerSettings}>
|
||||
|
||||
@@ -165,7 +165,9 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), {
|
||||
run: Promise.coroutine(function* () {
|
||||
yield this.updateBinaries();
|
||||
var steps = yield this.requiredSteps();
|
||||
console.log(steps);
|
||||
for (let step of steps) {
|
||||
console.log(step.name);
|
||||
_currentStep = step;
|
||||
step.percent = 0;
|
||||
while (true) {
|
||||
@@ -181,6 +183,7 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), {
|
||||
break;
|
||||
} catch (err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
_error = err;
|
||||
this.emit(this.ERROR_EVENT);
|
||||
} else {
|
||||
|
||||
@@ -7,7 +7,6 @@ module.exports = {
|
||||
exec: function (args, options) {
|
||||
options = options || {};
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(options);
|
||||
exec(args, options, (stderr, stdout, code) => {
|
||||
if (code) {
|
||||
reject(stderr);
|
||||
|
||||
Reference in New Issue
Block a user