Last flux changes for containers

This commit is contained in:
Jeffrey Morgan 2015-05-10 16:42:57 -07:00
parent 7989d48253
commit 0e5ce4cc2e
13 changed files with 139 additions and 94 deletions

View File

@ -24,10 +24,17 @@ class ContainerServerActions {
}
update (name, container) {
console.log(container);
this.dispatch({container});
dockerUtil.updateContainer(name, container);
}
clearPending () {
this.dispatch();
}
run (name, repo, tag) {
dockerUtil.run(name, repo, tag);
}
}
export default alt.createActions(ContainerServerActions);

View File

@ -10,6 +10,7 @@ class ContainerServerActions {
'muted',
'unmuted',
'progress',
'pending',
'updated',
'waiting'
);

View File

@ -5,8 +5,6 @@ var Radial = require('./Radial.react');
var ContainerHomePreview = require('./ContainerHomePreview.react');
var ContainerHomeLogs = require('./ContainerHomeLogs.react');
var ContainerHomeFolders = require('./ContainerHomeFolders.react');
var containerUtil = require('../utils/ContainerUtil');
var util = require ('../utils/Util');
var shell = require('shell');
var ContainerHome = React.createClass({
@ -100,7 +98,7 @@ var ContainerHome = React.createClass({
if (_.keys(this.props.ports) > 0) {
right = (
<div className="right">
<ContainerHomePreview />
<ContainerHomePreview ports={this.props.ports} defaultPort={this.props.defaultPort} />
<ContainerHomeFolders container={this.props.container} />
</div>
);

View File

@ -70,6 +70,7 @@ var ContainerHomePreview = React.createClass({
</div>
);
});
preview = (
<div className="web-preview wrapper">
<h4>IP &amp; Ports</h4>

View File

@ -1,13 +1,10 @@
var $ = require('jquery');
var React = require('react/addons');
var React = require('react');
var Router = require('react-router');
var ContainerStore = require('../stores/ContainerStore');
var metrics = require('../utils/MetricsUtil');
var ContainerListNewItem = React.createClass({
contextTypes: {
router: React.PropTypes.func
},
mixins: [Router.Navigation, Router.State],
handleItemMouseEnter: function () {
var $action = $(this.getDOMNode()).find('.action');
$action.show();
@ -16,22 +13,20 @@ var ContainerListNewItem = React.createClass({
var $action = $(this.getDOMNode()).find('.action');
$action.hide();
},
handleDelete: function () {
var self = this;
handleDelete: function (event) {
metrics.track('Deleted Container', {
from: 'list',
type: 'new'
});
var containers = ContainerStore.sorted();
$(self.getDOMNode()).fadeOut(300, () => {
if (containers.length > 0) {
var name = containers[0].Name;
this.context.router.transitionTo('containerHome', {name: name});
}
});
if (this.props.containers.length > 0 && this.getRoutes()[this.getRoutes().length - 2].name === 'new') {
var name = this.props.containers[0].Name;
this.transitionTo('containerHome', {name});
}
$(this.getDOMNode()).fadeOut(300);
event.preventDefault();
},
render: function () {
var self = this;
var action;
if (this.props.containers.length > 0) {
action = (
@ -42,7 +37,7 @@ var ContainerListNewItem = React.createClass({
}
return (
<Router.Link to="new">
<li className="new-container-item" onMouseEnter={self.handleItemMouseEnter} onMouseLeave={self.handleItemMouseLeave}>
<li className="new-container-item" onMouseEnter={this.handleItemMouseEnter} onMouseLeave={this.handleItemMouseLeave}>
<div className="state state-new"></div>
<div className="info">
<div className="name">

View File

@ -1,5 +1,4 @@
var _ = require('underscore');
var $ = require('jquery');
var React = require('react/addons');
var remote = require('remote');
var metrics = require('../utils/MetricsUtil');
@ -8,22 +7,31 @@ var ContainerUtil = require('../utils/ContainerUtil');
var containerActions = require('../actions/ContainerActions');
var ContainerSettingsGeneral = React.createClass({
mixins: [React.addons.LinkedStateMixin],
contextTypes: {
router: React.PropTypes.func
},
getInitialState: function () {
let env = ContainerUtil.env(this.props.container) || [];
env.push(['', '']);
console.log(env);
return {
slugName: null,
nameError: null,
pendingEnv: ContainerUtil.env(this.props.container) || {}
env: env
};
},
willReceiveProps: function () {
this.setState({
pendingEnv: ContainerUtil.env(this.props.container) || {}
});
shouldComponentUpdate: function (nextProps, nextState) {
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;
},
handleNameChange: function (e) {
@ -78,39 +86,54 @@ var ContainerSettingsGeneral = React.createClass({
metrics.track('Changed Container Name');
},
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);
});
handleSaveEnvVars: function () {
metrics.track('Saved Environment Variables');
containerActions.update(this.props.container.Name, {Env: envVarList});
let list = [];
_.each(this.state.env, kvp => {
let [key, value] = kvp;
if ((key && key.length) || (value && value.length)) {
list.push(key + '=' + value);
}
});
containerActions.update(this.props.container.Name, {Env: list});
},
handleAddPendingEnvVar: function () {
var newKey = $('#new-env-key').val();
var newVal = $('#new-env-val').val();
var newEnv = {};
newEnv[newKey] = newVal;
handleChangeEnvKey: function (index, event) {
let env = _.map(this.state.env, _.clone);
env[index][0] = event.target.value;
this.setState({
pendingEnv: _.extend(this.state.pendingEnv, newEnv)
env: env
});
},
handleChangeEnvVal: function (index, event) {
let env = _.map(this.state.env, _.clone);
env[index][1] = event.target.value;
this.setState({
env: env
});
},
handleAddEnvVar: function () {
let env = _.map(this.state.env, _.clone);
env.push(['', '']);
this.setState({
env: env
});
$('#new-env-key').val('');
$('#new-env-val').val('');
metrics.track('Added Pending Environment Variable');
},
handleRemovePendingEnvVar: function (key) {
var newEnv = _.omit(this.state.env, key);
handleRemoveEnvVar: function (index) {
let env = _.map(this.state.env, _.clone);
env.splice(index, 1);
if (env.length === 0) {
env.push(['', '']);
}
this.setState({
env: newEnv
env: env
});
metrics.track('Removed Environment Variable');
},
@ -131,8 +154,9 @@ var ContainerSettingsGeneral = React.createClass({
render: function () {
if (!this.props.container) {
return (<div></div>);
return false;
}
var willBeRenamedAs;
var btnSaveName = (
<a className="btn btn-action" onClick={this.handleSaveContainerName} disabled="disabled">Save</a>
@ -149,7 +173,8 @@ var ContainerSettingsGeneral = React.createClass({
<p><strong>{this.state.nameError}</strong></p>
);
}
var rename = (
let rename = (
<div className="settings-section">
<h3>Container Name</h3>
<div className="container-name">
@ -159,15 +184,25 @@ var ContainerSettingsGeneral = React.createClass({
{btnSaveName}
</div>
);
var pendingEnvVars = _.map(this.state.pendingEnv, (val, key) => {
let vars = _.map(this.state.env, (kvp, index) => {
let [key, val] = kvp;
let icon;
if (index === this.state.env.length - 1) {
icon = <a onClick={this.handleAddEnvVar} className="only-icon btn btn-positive small"><span className="icon icon-add-1"></span></a>;
} else {
icon = <a onClick={this.handleRemoveEnvVar.bind(this, index)} className="only-icon btn btn-action small"><span className="icon icon-cross"></span></a>;
}
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={this.handleRemovePendingEnvVar.bind(this, key)} className="only-icon btn btn-action small"><span className="icon icon-cross"></span></a>
<div key={index + ':' + key + '=' + val} 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}
</div>
);
});
return (
<div className="settings-panel">
{rename}
@ -178,14 +213,9 @@ var ContainerSettingsGeneral = React.createClass({
<div className="label-val">VALUE</div>
</div>
<div className="env-vars">
{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>
{vars}
</div>
<a className="btn btn-action" onClick={this.handleSaveEnvVar}>Save</a>
<a className="btn btn-action" onClick={this.handleSaveEnvVars}>Save</a>
</div>
<div className="settings-section">
<h3>Delete Container</h3>

View File

@ -4,12 +4,12 @@ var remote = require('remote');
var dialog = remote.require('dialog');
var shell = require('shell');
var metrics = require('../utils/MetricsUtil');
var ContainerStore = require('../stores/ContainerStore');
var containerActions = require('../actions/ContainerActions');
var ContainerSettingsVolumes = React.createClass({
handleChooseVolumeClick: function (dockerVol) {
var self = this;
dialog.showOpenDialog({properties: ['openDirectory', 'createDirectory']}, function (filenames) {
dialog.showOpenDialog({properties: ['openDirectory', 'createDirectory']}, (filenames) => {
if (!filenames) {
return;
}
@ -21,11 +21,8 @@ var ContainerSettingsVolumes = React.createClass({
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); }
});
containerActions.update(this.props.container.Name, {Binds: binds});
}
});
},
@ -38,11 +35,7 @@ var ContainerSettingsVolumes = React.createClass({
var binds = _.pairs(volumes).map(function (pair) {
return pair[1] + ':' + pair[0];
});
ContainerStore.updateContainer(this.props.container.Name, {
Binds: binds
}, function (err) {
if (err) { console.log(err); }
});
containerActions.update(this.props.container.Name, {Binds: binds});
},
handleOpenVolumeClick: function (path) {
metrics.track('Opened Volume Directory', {

View File

@ -1,6 +1,6 @@
var $ = require('jquery');
var _ = require('underscore');
var React = require('react/addons');
var React = require('react');
var Router = require('react-router');
var containerStore = require('../stores/ContainerStore');
var ContainerList = require('./ContainerList.react');
@ -62,7 +62,9 @@ var Containers = React.createClass({
});
let name = this.context.router.getCurrentParams().name;
if (name && !containers[name]) {
if (containerStore.getState().pending) {
this.context.router.transitionTo('pull');
} else if (name && !containers[name]) {
if (sorted.length) {
this.context.router.transitionTo('containerHome', {name: sorted[0].Name});
} else {
@ -72,7 +74,8 @@ var Containers = React.createClass({
this.setState({
containers: containers,
sorted: sorted
sorted: sorted,
pending: containerStore.getState().pending
});
},

View File

@ -1,14 +1,16 @@
var $ = require('jquery');
var React = require('react/addons');
var Router = require('react-router');
var RetinaImage = require('react-retina-image');
var metrics = require('../utils/MetricsUtil');
var OverlayTrigger = require('react-bootstrap').OverlayTrigger;
var Tooltip = require('react-bootstrap').Tooltip;
var util = require('../utils/Util');
var dockerUtil = require('../utils/DockerUtil');
var containerActions = require('../actions/ContainerActions');
var containerStore = require('../stores/ContainerStore');
var ImageCard = React.createClass({
mixins: [Router.Navigation],
getInitialState: function () {
return {
tags: [],
@ -28,7 +30,8 @@ var ImageCard = React.createClass({
from: 'search'
});
let name = containerStore.generateName(repository);
dockerUtil.run(name, repository, this.state.chosenTag);
containerActions.run(name, repository, this.state.chosenTag);
this.transitionTo('containerHome', {name});
},
handleTagOverlayClick: function (name) {
var $tagOverlay = $(this.getDOMNode()).find('.tag-overlay');

View File

@ -1,30 +1,32 @@
var React = require('react/addons');
var Router = require('react-router');
var shell = require('shell');
var ContainerStore = require('../stores/ContainerStore');
var containerActions = require('../actions/ContainerActions');
var containerStore = require('../stores/ContainerStore');
var metrics = require('../utils/MetricsUtil');
module.exports = React.createClass({
mixins: [Router.Navigation],
handleOpenClick: function () {
var repo = this.props.pending.repository;
var repo = this.props.pending.repo;
if (repo.indexOf('/') === -1) {
shell.openExternal(`https://registry.hub.docker.com/_/${this.props.pending.repository}`);
shell.openExternal(`https://registry.hub.docker.com/_/${this.props.pending.repo}`);
} else {
shell.openExternal(`https://registry.hub.docker.com/u/${this.props.pending.repository}`);
shell.openExternal(`https://registry.hub.docker.com/u/${this.props.pending.repo}`);
}
},
handleCancelClick: function () {
metrics.track('Canceled Click-To-Pull');
ContainerStore.clearPending();
containerActions.clearPending();
this.context.router.transitionTo('new');
},
handleConfirmClick: function () {
metrics.track('Created Container', {
from: 'click-to-pull'
});
ContainerStore.clearPending();
ContainerStore.create(this.props.pending.repository, this.props.pending.tag, function () {});
containerActions.clearPending();
let name = containerStore.generateName(this.props.pending.repo);
containerActions.run(name, this.props.pending.repo, this.props.pending.tag);
},
render: function () {
if (!this.props.pending) {
@ -34,7 +36,7 @@ module.exports = React.createClass({
<div className="details">
<div className="new-container-pull">
<div className="content">
<h1>You&#39;re about to download and run <a onClick={this.handleOpenClick}>{this.props.pending.repository}:{this.props.pending.tag}</a>.</h1>
<h1>You&#39;re about to download and run <a onClick={this.handleOpenClick}>{this.props.pending.repo}:{this.props.pending.tag}</a>.</h1>
<h1>Please confirm to create the container.</h1>
<div className="buttons">
<a className="btn btn-action" onClick={this.handleCancelClick}>Cancel</a> <a onClick={this.handleConfirmClick} className="btn btn-action">Confirm</a>

View File

@ -11,6 +11,9 @@ class ContainerStore {
// Blacklist of containers to avoid updating
this.muted = {};
// Pending container to create
this.pending = null;
}
start ({name}) {
@ -106,7 +109,7 @@ class ContainerStore {
this.setState({containers});
}
error ({ name, error }) {
error ({name, error}) {
let containers = this.containers;
if (containers[name]) {
containers[name].Error = error;
@ -114,6 +117,15 @@ class ContainerStore {
this.setState({containers});
}
pending ({repo, tag}) {
let pending = {repo, tag};
this.setState({pending});
}
clearPending () {
this.setState({pending: null});
}
static generateName (repo) {
let base = _.last(repo.split('/'));
let count = 1;

View File

@ -4,13 +4,13 @@ var docker = require('../utils/DockerUtil');
var ContainerUtil = {
env: function (container) {
if (!container || !container.Config || !container.Config.Env) {
return {};
return [];
}
return _.object(container.Config.Env.map(function (env) {
return _.map(container.Config.Env, env => {
var i = env.indexOf('=');
var splits = [env.slice(0, i), env.slice(i + 1)];
return splits;
}));
});
},
// TODO: inject host here instead of requiring Docker

View File

@ -1,6 +1,6 @@
var util = require('./Util');
var parseUri = require('parseUri');
var containerStore = require('../stores/ContainerStore');
var containerServerActions = require('../actions/ContainerServerActions');
module.exports = {
TYPE_WHITELIST: ['repository'],
@ -52,7 +52,7 @@ module.exports = {
}
if (type === 'repository' && method === 'run') {
containerStore.setPending(repo, 'latest');
containerServerActions.pending({repo, tag: 'latest'});
return true;
}
return false;