Error handling, updating state

This commit is contained in:
Jeffrey Morgan 2015-05-11 17:16:50 -07:00
parent 239c54e2ad
commit 32f129c5bd
13 changed files with 104 additions and 75 deletions

View File

@ -65,12 +65,15 @@
"bugsnag-js": "^2.4.7",
"classnames": "^1.2.0",
"coveralls": "^2.11.2",
"deep-extend": "^0.4.0",
"dockerode": "^2.1.1",
"exec": "0.2.0",
"install": "^0.1.8",
"jquery": "^2.1.3",
"mixpanel": "0.2.0",
"mkdirp": "^0.5.0",
"node-uuid": "^1.4.3",
"npm": "^2.9.1",
"object-assign": "^2.0.0",
"parseUri": "^1.2.3-2",
"react": "^0.13.1",

View File

@ -1,7 +1,7 @@
import alt from '../alt';
import dockerUtil from '../utils/DockerUtil';
class ContainerServerActions {
class ContainerActions {
start (name) {
this.dispatch({name});
dockerUtil.start(name);
@ -12,7 +12,6 @@ class ContainerServerActions {
dockerUtil.destroy(name);
}
// TODO: don't require all container data for this method
rename (name, newName) {
this.dispatch({name, newName});
dockerUtil.rename(name, newName);
@ -24,7 +23,7 @@ class ContainerServerActions {
}
update (name, container) {
this.dispatch({container});
this.dispatch({name, container});
dockerUtil.updateContainer(name, container);
}
@ -37,4 +36,4 @@ class ContainerServerActions {
}
}
export default alt.createActions(ContainerServerActions);
export default alt.createActions(ContainerActions);

View File

@ -8,9 +8,10 @@ class ContainerServerActions {
'destroyed',
'error',
'muted',
'unmuted',
'progress',
'pending',
'progress',
'started',
'unmuted',
'updated',
'waiting'
);

View File

@ -12,14 +12,19 @@ var ContainerDetails = React.createClass({
},
render: function () {
if (!this.props.container) {
return false;
}
let ports = containerUtil.ports(this.props.container);
let defaultPort = _.find(_.keys(ports), port => {
return util.webPorts.indexOf(port) !== -1;
});
return (
<div className="details">
<ContainerDetailsHeader {...this.props} defaultPort={defaultPort} ports={ports}/>
<ContainerDetailsSubheader {...this.props}/>
<ContainerDetailsSubheader {...this.props} defaultPort={defaultPort} ports={ports}/>
<Router.RouteHandler {...this.props} defaultPort={defaultPort} ports={ports}/>
</div>
);

View File

@ -7,7 +7,9 @@ var ContainerDetailsHeader = React.createClass({
return false;
}
if (this.props.container.State.Running && !this.props.container.State.Paused && !this.props.container.State.ExitCode && !this.props.container.State.Restarting) {
if (this.props.container.State.Updating) {
state = <span className="status downloading">UPDATING</span>;
} else if (this.props.container.State.Running && !this.props.container.State.Paused && !this.props.container.State.ExitCode && !this.props.container.State.Restarting) {
state = <span className="status running">RUNNING</span>;
} else if (this.props.container.State.Restarting) {
state = <span className="status restarting">RESTARTING</span>;

View File

@ -20,31 +20,31 @@ var ContainerDetailsSubheader = React.createClass({
if (!this.props.container) {
return false;
}
return (!this.props.container.State.Running || !this.props.defaultPort);
return (!this.props.container.State.Running || !this.props.defaultPort || this.props.container.State.Updating);
},
disableRestart: function () {
if (!this.props.container) {
return false;
}
return (this.props.container.State.Downloading || this.props.container.State.Restarting);
return (this.props.container.State.Downloading || this.props.container.State.Restarting || this.props.container.State.Updating);
},
disableStop: function () {
if (!this.props.container) {
return false;
}
return (this.props.container.State.Downloading || this.props.container.State.ExitCode || !this.props.container.State.Running);
return (this.props.container.State.Downloading || this.props.container.State.ExitCode || !this.props.container.State.Running || this.props.container.State.Updating);
},
disableStart: function () {
if (!this.props.container) {
return false;
}
return (this.props.container.State.Downloading || this.props.container.State.Running);
return (this.props.container.State.Downloading || this.props.container.State.Running || this.props.container.State.Updating);
},
disableTerminal: function () {
if (!this.props.container) {
return false;
}
return (!this.props.container.State.Running);
return (!this.props.container.State.Running || this.props.container.State.Updating);
},
disableTab: function () {
if (!this.props.container) {

View File

@ -35,12 +35,16 @@ var ContainerHome = React.createClass({
},
render: function () {
if (!this.props.container) {
return;
}
let body;
if (this.props.error) {
if (this.props.container.Error) {
body = (
<div className="details-progress">
<h3>An error occurred:</h3>
<h2>{this.props.error.statusCode} {this.props.error.reason} - {this.props.error.json}</h2>
<h2>{this.props.container.Error.message}</h2>
<h3>If you feel that this error is invalid, please <a onClick={this.handleErrorClick}>file a ticket on our GitHub repo.</a></h3>
<Radial progress={100} error={true} thick={true} transparent={true}/>
</div>

View File

@ -22,6 +22,14 @@ module.exports = React.createClass({
LogStore.on(LogStore.SERVER_LOGS_EVENT, this.update);
LogStore.fetch(this.props.container.Name);
},
componentWillReceiveProps: function (nextProps) {
if (this.props.container && nextProps.container && this.props.container.Name !== nextProps.container.Name) {
LogStore.detach(this.props.container.Name);
LogStore.fetch(nextProps.container.Name);
}
},
componentWillUnmount: function() {
if (!this.props.container) {
return;

View File

@ -16,7 +16,6 @@ var ContainerSettingsGeneral = React.createClass({
getInitialState: function () {
let env = ContainerUtil.env(this.props.container) || [];
env.push(['', '']);
console.log(env);
return {
slugName: null,
nameError: null,
@ -28,9 +27,7 @@ var ContainerSettingsGeneral = React.createClass({
if (nextState.slugName !== this.state.slugName || nextState.nameError !== this.state.nameError) {
return true;
}
if (nextState.env.length === this.state.env.length) {
return false;
}
return true;
},
@ -195,7 +192,7 @@ var ContainerSettingsGeneral = React.createClass({
}
return (
<div key={index + ':' + key + '=' + val} className="keyval-row">
<div className="keyval-row">
<input type="text" className="key line" defaultValue={key} onChange={this.handleChangeEnvKey.bind(this, index)}></input>
<input type="text" className="val line" defaultValue={val} onChange={this.handleChangeEnvVal.bind(this, index)}></input>
{icon}
@ -215,7 +212,7 @@ var ContainerSettingsGeneral = React.createClass({
<div className="env-vars">
{vars}
</div>
<a className="btn btn-action" onClick={this.handleSaveEnvVars}>Save</a>
<a className="btn btn-action" disabled={this.props.container.State.Updating} onClick={this.handleSaveEnvVars}>Save</a>
</div>
<div className="settings-section">
<h3>Delete Container</h3>

View File

@ -22,7 +22,7 @@ var ContainerSettingsVolumes = React.createClass({
return pair[1] + ':' + pair[0];
});
containerActions.update(this.props.container.Name, {Binds: binds});
containerActions.update(this.props.container.Name, {Binds: binds, Volumes: volumes});
}
});
},
@ -35,7 +35,7 @@ var ContainerSettingsVolumes = React.createClass({
var binds = _.pairs(volumes).map(function (pair) {
return pair[1] + ':' + pair[0];
});
containerActions.update(this.props.container.Name, {Binds: binds});
containerActions.update(this.props.container.Name, {Binds: binds, Volumes: volumes});
},
handleOpenVolumeClick: function (path) {
metrics.track('Opened Volume Directory', {
@ -45,24 +45,24 @@ var ContainerSettingsVolumes = React.createClass({
},
render: function () {
if (!this.props.container) {
return (<div></div>);
return false;
}
var self = this;
var volumes = _.map(self.props.container.Volumes, function (val, key) {
var volumes = _.map(this.props.container.Volumes, (val, key) => {
if (!val || val.indexOf(process.env.HOME) === -1) {
val = (
<span>
<a className="value-right">No Folder</a>
<a className="btn btn-action small" onClick={self.handleChooseVolumeClick.bind(self, key)}>Change</a>
<a className="btn btn-action small" onClick={self.handleRemoveVolumeClick.bind(self, key)}>Remove</a>
<a className="btn btn-action small" disabled={this.props.container.State.Updating} onClick={this.handleChooseVolumeClick.bind(this, key)}>Change</a>
<a className="btn btn-action small" disabled={this.props.container.State.Updating} onClick={this.handleRemoveVolumeClick.bind(this, key)}>Remove</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-action small" onClick={self.handleChooseVolumeClick.bind(self, key)}>Change</a>
<a className="btn btn-action small" onClick={self.handleRemoveVolumeClick.bind(self, key)}>Remove</a>
<a className="value-right" onClick={this.handleOpenVolumeClick.bind(this, val)}>{val.replace(process.env.HOME, '~')}</a>
<a className="btn btn-action small" disabled={this.props.container.State.Updating} onClick={this.handleChooseVolumeClick.bind(this, key)}>Change</a>
<a className="btn btn-action small" disabled={this.props.container.State.Updating} onClick={this.handleRemoveVolumeClick.bind(this, key)}>Remove</a>
</span>
);
}

View File

@ -195,7 +195,7 @@ var Containers = React.createClass({
<div className="sidebar-buttons-padding"></div>
</section>
</div>
<Router.RouteHandler pending={this.state.pending} containers={this.state.containers} container={container} error={this.state.error}/>
<Router.RouteHandler pending={this.state.pending} containers={this.state.containers} container={container}/>
</div>
</div>
);

View File

@ -1,4 +1,5 @@
import _ from 'underscore';
import deepExtend from 'deep-extend';
import alt from '../alt';
import containerServerActions from '../actions/ContainerServerActions';
import containerActions from '../actions/ContainerActions';
@ -9,13 +10,18 @@ class ContainerStore {
this.bindActions(containerServerActions);
this.containers = {};
// Blacklist of containers to avoid updating
this.muted = {};
// Pending container to create
this.pending = null;
}
error ({name, error}) {
let containers = this.containers;
if (containers[name]) {
containers[name].Error = error;
}
this.setState({containers});
}
start ({name}) {
let containers = this.containers;
if (containers[name]) {
@ -24,6 +30,15 @@ class ContainerStore {
}
}
started ({name}) {
let containers = this.containers;
if (containers[name]) {
containers[name].State.Starting = false;
containers[name].State.Updating = false;
this.setState({containers});
}
}
stop ({name}) {
let containers = this.containers;
if (containers[name]) {
@ -36,6 +51,11 @@ class ContainerStore {
let containers = this.containers;
let data = containers[name];
data.Name = newName;
if (data.State) {
data.State.Updating = true;
}
containers[newName] = data;
delete containers[name];
this.setState({containers});
@ -44,23 +64,32 @@ class ContainerStore {
added ({container}) {
let containers = this.containers;
containers[container.Name] = container;
delete this.muted[container.Name];
this.setState({containers});
}
update ({name, container}) {
let containers = this.containers;
if (containers[name] && containers[name].State && containers[name].State.Updating) {
return;
}
deepExtend(containers[name], container);
if (containers[name].State) {
containers[name].State.Updating = true;
}
this.setState({containers});
}
updated ({container}) {
if (this.muted[container.Name]) {
let containers = this.containers;
if (!containers[container.Name] || containers[container.Name].State.Updating) {
return;
}
let containers = this.containers;
if (!containers[container.Name]) {
return;
}
containers[container.Name] = container;
if (container.State.Running) {
delete container.State.Starting;
}
this.setState({containers});
}
@ -87,20 +116,17 @@ class ContainerStore {
let container = _.find(_.values(this.containers), container => {
return container.Id === name || container.Name === name;
});
if (container && !this.muted[container.Name]) {
if (container && container.State && container.State.Updating) {
return;
}
if (container) {
delete containers[container.Name];
this.setState({containers});
}
}
muted ({name}) {
this.muted[name] = true;
}
unmuted ({name}) {
this.muted[name] = false;
}
waiting({name, waiting}) {
let containers = this.containers;
if (containers[name]) {
@ -109,14 +135,6 @@ class ContainerStore {
this.setState({containers});
}
error ({name, error}) {
let containers = this.containers;
if (containers[name]) {
containers[name].Error = error;
}
this.setState({containers});
}
pending ({repo, tag}) {
let pending = {repo, tag};
this.setState({pending});
@ -127,10 +145,10 @@ class ContainerStore {
}
static generateName (repo) {
let base = _.last(repo.split('/'));
let count = 1;
let name = base;
let names = _.keys(this.getState().containers);
const base = _.last(repo.split('/'));
const names = _.keys(this.getState().containers);
var count = 1;
var name = base;
while (true) {
if (names.indexOf(name) === -1) {
return name;

View File

@ -79,7 +79,7 @@ export default {
containerServerActions.error({name, error});
return;
}
containerServerActions.unmuted({name});
containerServerActions.started({name, error});
this.fetchContainer(name);
});
},
@ -184,9 +184,6 @@ export default {
updateContainer (name, data) {
let existing = this.client.getContainer(name);
existing.inspect((error, existingData) => {
if (error) {
return;
}
if (error) {
containerServerActions.error({name, error});
return;
@ -202,7 +199,6 @@ export default {
}
var fullData = _.extend(existingData, data);
containerServerActions.muted({name});
this.createContainer(name, fullData);
});
},
@ -213,7 +209,6 @@ export default {
containerServerActions.error({name, error});
return;
}
this.fetchAllContainers();
var oldPath = path.join(util.home(), 'Kitematic', name);
var newPath = path.join(util.home(), 'Kitematic', newName);
@ -223,7 +218,6 @@ export default {
containerServerActions.error({newName, error});
}
rimraf(newPath, () => {
console.log('removed');
if (fs.existsSync(oldPath)) {
fs.renameSync(oldPath, newPath);
}
@ -295,9 +289,7 @@ export default {
containerServerActions.destroyed({id: name});
var volumePath = path.join(util.home(), 'Kitematic', name);
if (fs.existsSync(volumePath)) {
rimraf(volumePath, function (err) {
console.log(err);
});
rimraf(volumePath, () => {});
}
});
});