mirror of https://github.com/docker/docs.git
Merge remote-tracking branch 'origin/sean-polish'
Conflicts: src/Radial.react.js src/Setup.react.js styles/radial.less
This commit is contained in:
parent
e8f87e1ebe
commit
c338678094
|
@ -16,7 +16,7 @@
|
||||||
"release": "gulp release",
|
"release": "gulp release",
|
||||||
"release:beta": "gulp release --beta",
|
"release:beta": "gulp release --beta",
|
||||||
"preinstall": "./deps",
|
"preinstall": "./deps",
|
||||||
"lint": "jsxhint src/**/* && jsxhint browser/**/*"
|
"lint": "jsxhint src && jsxhint browser"
|
||||||
},
|
},
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,10 @@ var React = require('react/addons');
|
||||||
var ContainerDetailsHeader = React.createClass({
|
var ContainerDetailsHeader = React.createClass({
|
||||||
render: function () {
|
render: function () {
|
||||||
var state;
|
var state;
|
||||||
if (this.props.container.State.Running) {
|
if (!this.props.container) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.props.container.State.Running && !this.props.container.State.Paused && !this.props.container.State.Restarting) {
|
||||||
state = <span className="status running">RUNNING</span>;
|
state = <span className="status running">RUNNING</span>;
|
||||||
} else if (this.props.container.State.Restarting) {
|
} else if (this.props.container.State.Restarting) {
|
||||||
state = <span className="status restarting">RESTARTING</span>;
|
state = <span className="status restarting">RESTARTING</span>;
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
var $ = require('jquery');
|
||||||
|
var React = require('react/addons');
|
||||||
|
var exec = require('exec');
|
||||||
|
var path = require('path');
|
||||||
|
var ContainerStore = require('./ContainerStore');
|
||||||
|
var ContainerUtil = require('./ContainerUtil');
|
||||||
|
var boot2docker = require('./Boot2Docker');
|
||||||
|
var RetinaImage = require('react-retina-image');
|
||||||
|
var Router = require('react-router');
|
||||||
|
|
||||||
|
var ContainerDetailsSubheader = React.createClass({
|
||||||
|
mixins: [Router.State, Router.Navigation],
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
defaultPort: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentWillReceiveProps: function () {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
init: function () {
|
||||||
|
this.setState({
|
||||||
|
currentRoute: _.last(this.getRoutes()).name
|
||||||
|
});
|
||||||
|
var container = ContainerStore.container(this.getParams().name);
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
disableRun: function () {
|
||||||
|
if (!this.props.container) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (!this.props.container.State.Running || !this.state.defaultPort);
|
||||||
|
},
|
||||||
|
disableRestart: function () {
|
||||||
|
if (!this.props.container) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (this.props.container.State.Downloading || this.props.container.State.Restarting);
|
||||||
|
},
|
||||||
|
disableTerminal: function () {
|
||||||
|
if (!this.props.container) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (!this.props.container.State.Running);
|
||||||
|
},
|
||||||
|
disableTab: function () {
|
||||||
|
if (!this.props.container) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (this.props.container.State.Downloading);
|
||||||
|
},
|
||||||
|
showHome: function () {
|
||||||
|
if (!this.disableTab()) {
|
||||||
|
this.transitionTo('containerHome', {name: this.getParams().name});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showLogs: function () {
|
||||||
|
if (!this.disableTab()) {
|
||||||
|
this.transitionTo('containerLogs', {name: this.getParams().name});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showSettings: function () {
|
||||||
|
if (!this.disableTab()) {
|
||||||
|
this.transitionTo('containerSettings', {name: this.getParams().name});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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 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()
|
||||||
|
});
|
||||||
|
var tabHomeClasses = React.addons.classSet({
|
||||||
|
'tab': true,
|
||||||
|
'active': this.state.currentRoute === 'containerHome',
|
||||||
|
disabled: this.disableTab()
|
||||||
|
});
|
||||||
|
var tabLogsClasses = React.addons.classSet({
|
||||||
|
'tab': true,
|
||||||
|
'active': this.state.currentRoute === 'containerLogs',
|
||||||
|
disabled: this.disableTab()
|
||||||
|
});
|
||||||
|
var tabSettingsClasses = React.addons.classSet({
|
||||||
|
'tab': true,
|
||||||
|
'active': this.state.currentRoute && (this.state.currentRoute.indexOf('containerSettings') >= 0),
|
||||||
|
disabled: this.disableTab()
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ContainerDetailsSubheader;
|
|
@ -18,7 +18,7 @@ var Radial = require('./Radial.react');
|
||||||
|
|
||||||
var _oldHeight = 0;
|
var _oldHeight = 0;
|
||||||
|
|
||||||
var ContainerDetails = React.createClass({
|
var ContainerDetailsbak = React.createClass({
|
||||||
mixins: [Router.State, Router.Navigation],
|
mixins: [Router.State, Router.Navigation],
|
||||||
PAGE_HOME: 'home',
|
PAGE_HOME: 'home',
|
||||||
PAGE_LOGS: 'logs',
|
PAGE_LOGS: 'logs',
|
||||||
|
@ -94,15 +94,32 @@ var ContainerDetails = React.createClass({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
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 () {
|
showHome: function () {
|
||||||
this.setState({
|
if (!this.disableTab()) {
|
||||||
page: this.PAGE_HOME
|
/*this.setState({
|
||||||
});
|
page: this.PAGE_HOME
|
||||||
|
});*/
|
||||||
|
this.transitionTo('containerHome', {name: this.getParams().name});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
showLogs: function () {
|
showLogs: function () {
|
||||||
this.setState({
|
if (!this.disableTab()) {
|
||||||
page: this.PAGE_LOGS
|
this.setState({
|
||||||
});
|
page: this.PAGE_LOGS
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
showPorts: function () {
|
showPorts: function () {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -115,20 +132,40 @@ var ContainerDetails = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
showSettings: function () {
|
showSettings: function () {
|
||||||
this.setState({
|
if (!this.disableTab()) {
|
||||||
page: this.PAGE_SETTINGS
|
this.setState({
|
||||||
});
|
page: this.PAGE_SETTINGS
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
handleView: function () {
|
handleRun: function () {
|
||||||
console.log('CLICKED');
|
if (this.state.defaultPort && !this.disableRun()) {
|
||||||
console.log(this.state.ports);
|
|
||||||
console.log(this.state.defaultPort);
|
|
||||||
if (this.state.defaultPort) {
|
|
||||||
exec(['open', this.state.ports[this.state.defaultPort].url], function (err) {
|
exec(['open', this.state.ports[this.state.defaultPort].url], function (err) {
|
||||||
if (err) { throw 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) {
|
handleViewLink: function (url) {
|
||||||
exec(['open', url], function (err) {
|
exec(['open', url], function (err) {
|
||||||
if (err) { throw err; }
|
if (err) { throw err; }
|
||||||
|
@ -171,23 +208,6 @@ var ContainerDetails = React.createClass({
|
||||||
if (err) { throw err; }
|
if (err) { throw err; }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleRestart: function () {
|
|
||||||
ContainerStore.restart(this.props.container.Name, function (err) {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleTerminal: function () {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleSaveContainerName: function () {
|
handleSaveContainerName: function () {
|
||||||
var newName = $('#input-container-name').val();
|
var newName = $('#input-container-name').val();
|
||||||
if (newName === this.props.container.Name) {
|
if (newName === this.props.container.Name) {
|
||||||
|
@ -515,19 +535,19 @@ var ContainerDetails = React.createClass({
|
||||||
var tabHomeClasses = React.addons.classSet({
|
var tabHomeClasses = React.addons.classSet({
|
||||||
'tab': true,
|
'tab': true,
|
||||||
'active': this.state.page === this.PAGE_HOME,
|
'active': this.state.page === this.PAGE_HOME,
|
||||||
disabled: this.props.container.State.Downloading
|
disabled: this.disableTab()
|
||||||
});
|
});
|
||||||
|
|
||||||
var tabLogsClasses = React.addons.classSet({
|
var tabLogsClasses = React.addons.classSet({
|
||||||
'tab': true,
|
'tab': true,
|
||||||
'active': this.state.page === this.PAGE_LOGS,
|
'active': this.state.page === this.PAGE_LOGS,
|
||||||
disabled: this.props.container.State.Downloading
|
disabled: this.disableTab()
|
||||||
});
|
});
|
||||||
|
|
||||||
var tabSettingsClasses = React.addons.classSet({
|
var tabSettingsClasses = React.addons.classSet({
|
||||||
'tab': true,
|
'tab': true,
|
||||||
'active': this.state.page === this.PAGE_SETTINGS,
|
'active': this.state.page === this.PAGE_SETTINGS,
|
||||||
disabled: this.props.container.State.Downloading
|
disabled: this.disableTab()
|
||||||
});
|
});
|
||||||
|
|
||||||
/*var ports = _.map(_.pairs(self.state.ports), function (pair, index, list) {
|
/*var ports = _.map(_.pairs(self.state.ports), function (pair, index, list) {
|
||||||
|
@ -572,20 +592,35 @@ var ContainerDetails = React.createClass({
|
||||||
);
|
);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<div className="details">
|
<div className="details">
|
||||||
<ContainerDetailsHeader container={this.props.container} />
|
<ContainerDetailsHeader container={this.props.container} />
|
||||||
<div className="details-subheader">
|
<div className="details-subheader">
|
||||||
<div className="details-header-actions">
|
<div className="details-header-actions">
|
||||||
<div className="action" onMouseEnter={this.handleItemMouseEnterRun} onMouseLeave={this.handleItemMouseLeaveRun}>
|
<div className={runActionClass} onMouseEnter={this.handleItemMouseEnterRun} onMouseLeave={this.handleItemMouseLeaveRun}>
|
||||||
<span className="action-icon" onClick={this.handleView}><RetinaImage src="button-run.png"/></span>
|
<span className="action-icon" onClick={this.handleRun}><RetinaImage src="button-run.png"/></span>
|
||||||
<span className="btn-label run">Run</span>
|
<span className="btn-label run">Run</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="action" onMouseEnter={this.handleItemMouseEnterRestart} onMouseLeave={this.handleItemMouseLeaveRestart}>
|
<div className={restartActionClass} onMouseEnter={this.handleItemMouseEnterRestart} onMouseLeave={this.handleItemMouseLeaveRestart}>
|
||||||
<span className="action-icon" onClick={this.handleRestart}><RetinaImage src="button-restart.png"/></span>
|
<span className="action-icon" onClick={this.handleRestart}><RetinaImage src="button-restart.png"/></span>
|
||||||
<span className="btn-label restart">Restart</span>
|
<span className="btn-label restart">Restart</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="action" onMouseEnter={this.handleItemMouseEnterTerminal} onMouseLeave={this.handleItemMouseLeaveTerminal}>
|
<div className={terminalActionClass} onMouseEnter={this.handleItemMouseEnterTerminal} onMouseLeave={this.handleItemMouseLeaveTerminal}>
|
||||||
<span className="action-icon" onClick={this.handleTerminal}><RetinaImage src="button-terminal.png"/></span>
|
<span className="action-icon" onClick={this.handleTerminal}><RetinaImage src="button-terminal.png"/></span>
|
||||||
<span className="btn-label terminal">Terminal</span>
|
<span className="btn-label terminal">Terminal</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -602,4 +637,4 @@ var ContainerDetails = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = ContainerDetails;
|
module.exports = ContainerDetailsbak;
|
|
@ -1,105 +1,130 @@
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
var $ = require('jquery');
|
var $ = require('jquery');
|
||||||
var React = require('react/addons');
|
var React = require('react/addons');
|
||||||
var RetinaImage = require('react-retina-image');
|
var ContainerStore = require('./ContainerStore');
|
||||||
var path = require('path');
|
var Router = require('react-router');
|
||||||
var exec = require('exec');
|
var Radial = require('./Radial.react');
|
||||||
|
var ContainerHomePreview = require('./ContainerHomePreview.react');
|
||||||
|
var ContainerHomeLogs = require('./ContainerHomeLogs.react');
|
||||||
|
var ContainerHomeFolders = require('./ContainerHomeFolders.react');
|
||||||
|
var ContainerUtil = require('./ContainerUtil');
|
||||||
|
|
||||||
|
var resizeWindow = function () {
|
||||||
|
$('.left .wrapper').height(window.innerHeight - 240);
|
||||||
|
$('.right .wrapper').height(window.innerHeight / 2 - 100);
|
||||||
|
};
|
||||||
|
|
||||||
var ContainerHome = React.createClass({
|
var ContainerHome = React.createClass({
|
||||||
|
mixins: [Router.State, Router.Navigation],
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
ports: {},
|
||||||
|
defaultPort: null
|
||||||
|
};
|
||||||
|
},
|
||||||
handleResize: function () {
|
handleResize: function () {
|
||||||
$('.web-preview').height(window.innerHeight - 240);
|
resizeWindow();
|
||||||
$('.mini-logs').height(window.innerHeight / 2 - 100);
|
},
|
||||||
$('.folders').height(window.innerHeight / 2 - 150);
|
componentWillReceiveProps: function () {
|
||||||
|
this.init();
|
||||||
},
|
},
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
$('.web-preview').height(window.innerHeight - 240);
|
this.init();
|
||||||
$('.mini-logs').height(window.innerHeight / 2 - 100);
|
ContainerStore.on(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
|
||||||
$('.folders').height(window.innerHeight / 2 - 150);
|
resizeWindow();
|
||||||
window.addEventListener('resize', this.handleResize);
|
window.addEventListener('resize', this.handleResize);
|
||||||
},
|
},
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
|
ContainerStore.removeListener(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
},
|
},
|
||||||
componentDidUpdate: function () {
|
componentDidUpdate: function () {
|
||||||
// Scroll logs to bottom
|
resizeWindow();
|
||||||
$('.web-preview').height(window.innerHeight - 240);
|
|
||||||
$('.mini-logs').height(window.innerHeight / 2 - 100);
|
|
||||||
$('.folders').height(window.innerHeight / 2 - 150);
|
|
||||||
var parent = $('.mini-logs');
|
|
||||||
if (parent.length) {
|
|
||||||
if (parent.scrollTop() >= this._oldHeight) {
|
|
||||||
parent.stop();
|
|
||||||
parent.scrollTop(parent[0].scrollHeight - parent.height());
|
|
||||||
}
|
|
||||||
this._oldHeight = parent[0].scrollHeight - parent.height();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
handleClickFolder: function (path) {
|
init: function () {
|
||||||
exec(['open', path], function (err) {
|
var container = ContainerStore.container(this.getParams().name);
|
||||||
if (err) { throw err; }
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}),
|
||||||
|
progress: ContainerStore.progress(this.getParams().name)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleClickPreview: function () {
|
updateProgress: function (name) {
|
||||||
if (this.props.defaultPort) {
|
if (name === this.getParams().name) {
|
||||||
exec(['open', this.props.ports[this.props.defaultPort].url], function (err) {
|
this.setState({
|
||||||
if (err) { throw err; }
|
progress: ContainerStore.progress(name)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
var preview;
|
var body;
|
||||||
if (this.props.defaultPort) {
|
if (this.props.container && this.props.container.State.Downloading) {
|
||||||
preview = (
|
if (this.state.progress) {
|
||||||
<div className="web-preview">
|
body = (
|
||||||
<h4>Web Preview</h4>
|
<div className="details-progress">
|
||||||
<div className="widget">
|
<h2>Downloading Image</h2>
|
||||||
<iframe sandbox="allow-same-origin allow-scripts" src={this.props.ports[this.props.defaultPort].url} scrolling="no"></iframe>
|
<Radial progress={Math.min(Math.round(this.state.progress * 100), 99)} thick={true} gray={true}/>
|
||||||
<div className="iframe-overlay" onClick={this.handleClickPreview}><span className="icon icon-upload-2"></span><div className="text">Open in Browser</div></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="subtext">Not showing correctly?</div>
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
console.log(this.props.container.Volumes);
|
|
||||||
var self = this;
|
|
||||||
var folders = _.map(self.props.container.Volumes, function (val, key) {
|
|
||||||
var firstFolder = key.split(path.sep)[1];
|
|
||||||
if (!val || val.indexOf(process.env.HOME) === -1) {
|
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
return (
|
body = (
|
||||||
<div key={key} className="folder" onClick={self.handleClickFolder.bind(self, val)}>
|
<div className="details-progress">
|
||||||
<RetinaImage src="folder.png" />
|
<h2>Connecting to Docker Hub</h2>
|
||||||
<div className="text">{firstFolder}</div>
|
<Radial spin="true" progress="90" thick={true} transparent={true}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
return (
|
if (this.state.defaultPort) {
|
||||||
<div className="details-panel home">
|
body = (
|
||||||
<div className="content">
|
<div className="details-panel home">
|
||||||
<div className="left">
|
<div className="content">
|
||||||
{preview}
|
<div className="left">
|
||||||
</div>
|
<ContainerHomePreview />
|
||||||
<div className="right">
|
</div>
|
||||||
<div className="mini-logs">
|
<div className="right">
|
||||||
<h4>Logs</h4>
|
<ContainerHomeLogs />
|
||||||
<div className="widget">
|
<ContainerHomeFolders container={this.props.container} />
|
||||||
{this.props.logs}
|
|
||||||
<div className="mini-logs-overlay"><span className="icon icon-scale-spread-1"></span><div className="text">View Logs</div></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="folders">
|
</div>
|
||||||
<h4>Edit Files</h4>
|
);
|
||||||
<div className="widget">
|
} else {
|
||||||
{folders}
|
var right;
|
||||||
|
if (_.keys(this.state.ports) > 0) {
|
||||||
|
right = (
|
||||||
|
<div className="right">
|
||||||
|
<ContainerHomePreview />
|
||||||
|
<ContainerHomeFolders container={this.props.container} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
right = (
|
||||||
|
<div className="right">
|
||||||
|
<ContainerHomeFolders container={this.props.container} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
body = (
|
||||||
|
<div className="details-panel home">
|
||||||
|
<div className="content">
|
||||||
|
<div className="left">
|
||||||
|
<ContainerHomeLogs />
|
||||||
</div>
|
</div>
|
||||||
<div className="subtext">Change Folders</div>
|
{right}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
</div>
|
}
|
||||||
);
|
}
|
||||||
|
return body;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
var React = require('react/addons');
|
||||||
|
var RetinaImage = require('react-retina-image');
|
||||||
|
var path = require('path');
|
||||||
|
var exec = require('exec');
|
||||||
|
var Router = require('react-router');
|
||||||
|
|
||||||
|
var ContainerHomeFolder = React.createClass({
|
||||||
|
mixins: [Router.State, Router.Navigation],
|
||||||
|
handleClickFolder: function (path) {
|
||||||
|
exec(['open', path], function (err) {
|
||||||
|
if (err) { throw err; }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleClickChangeFolders: function () {
|
||||||
|
this.transitionTo('containerSettingsVolumes', {name: this.getParams().name});
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var folders;
|
||||||
|
if (this.props.container) {
|
||||||
|
var self = this;
|
||||||
|
folders = _.map(self.props.container.Volumes, function (val, key) {
|
||||||
|
var firstFolder = key.split(path.sep)[1];
|
||||||
|
if (!val || val.indexOf(process.env.HOME) === -1) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div key={key} className="folder" onClick={self.handleClickFolder.bind(self, val)}>
|
||||||
|
<RetinaImage src="folder.png" />
|
||||||
|
<div className="text">{firstFolder}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="folders wrapper">
|
||||||
|
<h4>Edit Files</h4>
|
||||||
|
<div className="widget">
|
||||||
|
{folders}
|
||||||
|
</div>
|
||||||
|
<div className="subtext" onClick={this.handleClickChangeFolders}>Change Folders</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ContainerHomeFolder;
|
|
@ -0,0 +1,64 @@
|
||||||
|
var $ = require('jquery');
|
||||||
|
var React = require('react/addons');
|
||||||
|
var ContainerStore = require('./ContainerStore');
|
||||||
|
var Router = require('react-router');
|
||||||
|
|
||||||
|
var ContainerHomeLogs = React.createClass({
|
||||||
|
mixins: [Router.State, Router.Navigation],
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
logs: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentWillReceiveProps: function () {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.init();
|
||||||
|
ContainerStore.on(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
ContainerStore.removeListener(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||||
|
},
|
||||||
|
componentDidUpdate: function () {
|
||||||
|
// Scroll logs to bottom
|
||||||
|
var parent = $('.mini-logs');
|
||||||
|
if (parent.length) {
|
||||||
|
if (parent.scrollTop() >= this._oldHeight) {
|
||||||
|
parent.stop();
|
||||||
|
parent.scrollTop(parent[0].scrollHeight - parent.height());
|
||||||
|
}
|
||||||
|
this._oldHeight = parent[0].scrollHeight - parent.height();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
init: function () {
|
||||||
|
this.updateLogs();
|
||||||
|
},
|
||||||
|
updateLogs: function (name) {
|
||||||
|
if (name && name !== this.getParams().name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
logs: ContainerStore.logs(this.getParams().name)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleClickLogs: function () {
|
||||||
|
this.transitionTo('containerLogs', {name: this.getParams().name});
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var logs = this.state.logs.map(function (l, i) {
|
||||||
|
return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>;
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div className="mini-logs wrapper">
|
||||||
|
<h4>Logs</h4>
|
||||||
|
<div className="widget">
|
||||||
|
{logs}
|
||||||
|
<div className="mini-logs-overlay" onClick={this.handleClickLogs}><span className="icon icon-scale-spread-1"></span><div className="text">View Logs</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ContainerHomeLogs;
|
|
@ -0,0 +1,97 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
var $ = require('jquery');
|
||||||
|
var React = require('react/addons');
|
||||||
|
var exec = require('exec');
|
||||||
|
var ContainerStore = require('./ContainerStore');
|
||||||
|
var ContainerUtil = require('./ContainerUtil');
|
||||||
|
var Router = require('react-router');
|
||||||
|
|
||||||
|
var ContainerHomePreview = React.createClass({
|
||||||
|
mixins: [Router.State, Router.Navigation],
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
ports: {},
|
||||||
|
defaultPort: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentWillReceiveProps: function () {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.init();
|
||||||
|
this.timer = setInterval(this.tick, 2000);
|
||||||
|
},
|
||||||
|
tick: function () {
|
||||||
|
if (document.getElementById('web-preview-frame')) {
|
||||||
|
var frameContent = document.getElementById('web-preview-frame').contentDocument;
|
||||||
|
var $body = $(frameContent.body);
|
||||||
|
if ($body.is(':empty')) {
|
||||||
|
document.getElementById('web-preview-frame').contentDocument.location.reload(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
},
|
||||||
|
init: function () {
|
||||||
|
var container = ContainerStore.container(this.getParams().name);
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleClickPreview: function () {
|
||||||
|
if (this.state.defaultPort) {
|
||||||
|
exec(['open', this.state.ports[this.state.defaultPort].url], function (err) {
|
||||||
|
if (err) { throw err; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClickNotShowingCorrectly: function () {
|
||||||
|
this.transitionTo('containerSettingsPorts', {name: this.getParams().name});
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var preview;
|
||||||
|
if (this.state.defaultPort) {
|
||||||
|
preview = (
|
||||||
|
<div className="web-preview wrapper">
|
||||||
|
<h4>Web Preview</h4>
|
||||||
|
<div className="widget">
|
||||||
|
<iframe id="web-preview-frame" name="disable-x-frame-options" sandbox="allow-same-origin allow-scripts" src={this.state.ports[this.state.defaultPort].url} scrolling="no"></iframe>
|
||||||
|
<div className="iframe-overlay" onClick={this.handleClickPreview}><span className="icon icon-upload-2"></span><div className="text">Open in Browser</div></div>
|
||||||
|
</div>
|
||||||
|
<div className="subtext" onClick={this.handleClickNotShowingCorrectly}>Not showing correctly?</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
var ports = _.map(_.pairs(this.state.ports), function (pair) {
|
||||||
|
var key = pair[0];
|
||||||
|
var val = pair[1];
|
||||||
|
return (
|
||||||
|
<div key={key} className="ip-port">
|
||||||
|
{val.display}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
preview = (
|
||||||
|
<div className="web-preview wrapper">
|
||||||
|
<h4>IP & Ports</h4>
|
||||||
|
<div className="widget">
|
||||||
|
<p>You can access this container from the outside using the following IP & Port(s):</p>
|
||||||
|
{ports}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return preview;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ContainerHomePreview;
|
|
@ -66,7 +66,7 @@ var ContainerListItem = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router.Link data-container={name} to="container" params={{name: container.Name}}>
|
<Router.Link data-container={name} to="containerDetail" params={{name: container.Name}}>
|
||||||
<li onMouseEnter={self.handleItemMouseEnter} onMouseLeave={self.handleItemMouseLeave}>
|
<li onMouseEnter={self.handleItemMouseEnter} onMouseLeave={self.handleItemMouseLeave}>
|
||||||
{state}
|
{state}
|
||||||
<div className="info">
|
<div className="info">
|
||||||
|
|
|
@ -3,6 +3,7 @@ var React = require('react/addons');
|
||||||
var Router = require('react-router');
|
var Router = require('react-router');
|
||||||
|
|
||||||
var ContainerListNewItem = React.createClass({
|
var ContainerListNewItem = React.createClass({
|
||||||
|
mixins: [Router.State, Router.Navigation],
|
||||||
handleItemMouseEnter: function () {
|
handleItemMouseEnter: function () {
|
||||||
var $action = $(this.getDOMNode()).find('.action');
|
var $action = $(this.getDOMNode()).find('.action');
|
||||||
$action.show();
|
$action.show();
|
||||||
|
@ -13,6 +14,7 @@ var ContainerListNewItem = React.createClass({
|
||||||
},
|
},
|
||||||
handleDelete: function () {
|
handleDelete: function () {
|
||||||
$(this.getDOMNode()).fadeOut();
|
$(this.getDOMNode()).fadeOut();
|
||||||
|
this.transitionTo('containers');
|
||||||
},
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
var $ = require('jquery');
|
||||||
|
var React = require('react/addons');
|
||||||
|
var ContainerStore = require('./ContainerStore');
|
||||||
|
var Router = require('react-router');
|
||||||
|
|
||||||
|
var ContainerLogs = React.createClass({
|
||||||
|
mixins: [Router.State],
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
logs: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentWillReceiveProps: function () {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.init();
|
||||||
|
ContainerStore.on(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
ContainerStore.removeListener(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||||
|
},
|
||||||
|
componentDidUpdate: function () {
|
||||||
|
// Scroll logs to bottom
|
||||||
|
var parent = $('.details-logs');
|
||||||
|
if (parent.length) {
|
||||||
|
if (parent.scrollTop() >= this._oldHeight) {
|
||||||
|
parent.stop();
|
||||||
|
parent.scrollTop(parent[0].scrollHeight - parent.height());
|
||||||
|
}
|
||||||
|
this._oldHeight = parent[0].scrollHeight - parent.height();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
init: function () {
|
||||||
|
this.updateLogs();
|
||||||
|
},
|
||||||
|
updateLogs: function (name) {
|
||||||
|
if (name && name !== this.getParams().name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
logs: ContainerStore.logs(this.getParams().name)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var logs = this.state.logs.map(function (l, i) {
|
||||||
|
return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>;
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div className="details-panel details-logs logs">
|
||||||
|
{logs}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ContainerLogs;
|
|
@ -0,0 +1,53 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
var React = require('react/addons');
|
||||||
|
var Router = require('react-router');
|
||||||
|
|
||||||
|
var ContainerSettings = React.createClass({
|
||||||
|
mixins: [Router.State, Router.Navigation],
|
||||||
|
componentWillReceiveProps: function () {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
init: function () {
|
||||||
|
var currentRoute = _.last(this.getRoutes()).name;
|
||||||
|
if (currentRoute === 'containerSettings') {
|
||||||
|
this.transitionTo('containerSettingsGeneral', {name: this.getParams().name});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var container = this.props.container;
|
||||||
|
if (!container) {
|
||||||
|
return (<div></div>);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="details-panel">
|
||||||
|
<div className="settings">
|
||||||
|
<div className="settings-menu">
|
||||||
|
<ul>
|
||||||
|
<Router.Link to="containerSettingsGeneral" params={{name: container.Name}}>
|
||||||
|
<li>
|
||||||
|
General
|
||||||
|
</li>
|
||||||
|
</Router.Link>
|
||||||
|
<Router.Link to="containerSettingsPorts" params={{name: container.Name}}>
|
||||||
|
<li>
|
||||||
|
Ports
|
||||||
|
</li>
|
||||||
|
</Router.Link>
|
||||||
|
<Router.Link to="containerSettingsVolumes" params={{name: container.Name}}>
|
||||||
|
<li>
|
||||||
|
Volumes
|
||||||
|
</li>
|
||||||
|
</Router.Link>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<Router.RouteHandler container={container}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ContainerSettings;
|
|
@ -0,0 +1,234 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
var $ = require('jquery');
|
||||||
|
var React = require('react/addons');
|
||||||
|
var Router = require('react-router');
|
||||||
|
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 containerNameSlugify = function (text) {
|
||||||
|
text = text.replace(/^\s+|\s+$/g, ''); // Trim
|
||||||
|
text = text.toLowerCase();
|
||||||
|
// Remove Accents
|
||||||
|
var from = "àáäâèéëêìíïîòóöôùúüûñç·/,:;";
|
||||||
|
var to = "aaaaeeeeiiiioooouuuunc-----";
|
||||||
|
for (var i=0, l=from.length ; i<l ; i++) {
|
||||||
|
text = text.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
|
||||||
|
}
|
||||||
|
text = text.replace(/[^a-z0-9 -_]/g, '') // Remove invalid chars
|
||||||
|
.replace(/\s+/g, '-') // Collapse whitespace and replace by -
|
||||||
|
.replace(/-+/g, '-') // Collapse dashes
|
||||||
|
.replace(/_+/g, '_'); // Collapse underscores
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
|
var ContainerSettingsGeneral = React.createClass({
|
||||||
|
mixins: [Router.State, Router.Navigation],
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
slugName: null,
|
||||||
|
env: {},
|
||||||
|
pendingEnv: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentWillReceiveProps: function () {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
init: function () {
|
||||||
|
var container = ContainerStore.container(this.getParams().name);
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
env: ContainerUtil.env(container),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleNameChange: function (e) {
|
||||||
|
var newName = e.target.value;
|
||||||
|
if (newName === this.state.slugName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!newName.length) {
|
||||||
|
this.setState({
|
||||||
|
slugName: null
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
slugName: containerNameSlugify(newName)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleNameOnKeyUp: function (e) {
|
||||||
|
if (e.keyCode === 13 && this.state.slugName) {
|
||||||
|
this.handleSaveContainerName();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSaveContainerName: function () {
|
||||||
|
var newName = this.state.slugName;
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
slugName: null
|
||||||
|
});
|
||||||
|
ContainerStore.updateContainer(this.props.container.Name, {
|
||||||
|
name: newName
|
||||||
|
}, function (err) {
|
||||||
|
this.transitionTo('containerSettingsGeneral', {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));
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
if (!this.props.container) {
|
||||||
|
return (<div></div>);
|
||||||
|
}
|
||||||
|
var willBeRenamedAs;
|
||||||
|
var btnSaveName = (
|
||||||
|
<a className="btn btn-action" onClick={this.handleSaveContainerName} disabled="disabled">Save</a>
|
||||||
|
);
|
||||||
|
if (this.state.slugName) {
|
||||||
|
willBeRenamedAs = (
|
||||||
|
<p>Will be renamed as: <strong>{this.state.slugName}</strong></p>
|
||||||
|
);
|
||||||
|
btnSaveName = (
|
||||||
|
<a className="btn btn-action" onClick={this.handleSaveContainerName}>Save</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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} onChange={this.handleNameChange} onKeyUp={this.handleNameOnKeyUp}></input>
|
||||||
|
{willBeRenamedAs}
|
||||||
|
</div>
|
||||||
|
{btnSaveName}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
var self = this;
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div className="settings-panel">
|
||||||
|
{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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ContainerSettingsGeneral;
|
|
@ -0,0 +1,85 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
var React = require('react/addons');
|
||||||
|
var Router = require('react-router');
|
||||||
|
var exec = require('exec');
|
||||||
|
var ContainerStore = require('./ContainerStore');
|
||||||
|
var ContainerUtil = require('./ContainerUtil');
|
||||||
|
|
||||||
|
var ContainerSettingsPorts = React.createClass({
|
||||||
|
mixins: [Router.State, Router.Navigation],
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
ports: {},
|
||||||
|
defaultPort: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentWillReceiveProps: function () {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
init: function () {
|
||||||
|
var container = ContainerStore.container(this.getParams().name);
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
if (!this.props.container) {
|
||||||
|
return (<div></div>);
|
||||||
|
}
|
||||||
|
var self = this;
|
||||||
|
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>
|
||||||
|
<input onChange={self.handleChangeDefaultPort.bind(self, key)} type="checkbox" checked={self.state.defaultPort === key}/> <label>Web Preview</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div className="settings-panel">
|
||||||
|
<div className="settings-section">
|
||||||
|
<h3>Configure Ports</h3>
|
||||||
|
<div className="table ports">
|
||||||
|
<div className="table-labels">
|
||||||
|
<div className="label-left">DOCKER PORT</div>
|
||||||
|
<div className="label-right">MAC PORT</div>
|
||||||
|
</div>
|
||||||
|
{ports}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ContainerSettingsPorts;
|
|
@ -0,0 +1,82 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
var React = require('react/addons');
|
||||||
|
var Router = require('react-router');
|
||||||
|
var remote = require('remote');
|
||||||
|
var exec = require('exec');
|
||||||
|
var dialog = remote.require('dialog');
|
||||||
|
var ContainerStore = require('./ContainerStore');
|
||||||
|
|
||||||
|
var ContainerSettingsVolumes = React.createClass({
|
||||||
|
mixins: [Router.State, Router.Navigation],
|
||||||
|
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; }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
if (!this.props.container) {
|
||||||
|
return (<div></div>);
|
||||||
|
}
|
||||||
|
var self = this;
|
||||||
|
var volumes = _.map(self.props.container.Volumes, function (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>
|
||||||
|
</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>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div key={key} className="table-values">
|
||||||
|
<span className="value-left">{key}</span><span className="icon icon-arrow-right"></span>
|
||||||
|
{val}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div className="settings-panel">
|
||||||
|
<div className="settings-section">
|
||||||
|
<h3>Configure Volumes</h3>
|
||||||
|
<div className="table volumes">
|
||||||
|
<div className="table-labels">
|
||||||
|
<div className="label-left">DOCKER FOLDER</div>
|
||||||
|
<div className="label-right">MAC FOLDER</div>
|
||||||
|
</div>
|
||||||
|
{volumes}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ContainerSettingsVolumes;
|
|
@ -22,7 +22,7 @@ var ContainerUtil = {
|
||||||
if (value && value.length) {
|
if (value && value.length) {
|
||||||
var port = value[0].HostPort;
|
var port = value[0].HostPort;
|
||||||
localUrl = 'http://' + ip + ':' + port;
|
localUrl = 'http://' + ip + ':' + port;
|
||||||
localUrlDisplay = ip + ': ' + port;
|
localUrlDisplay = ip + ':' + port;
|
||||||
}
|
}
|
||||||
res[dockerPort] = {
|
res[dockerPort] = {
|
||||||
url: localUrl,
|
url: localUrl,
|
||||||
|
|
|
@ -20,7 +20,7 @@ var Containers = React.createClass({
|
||||||
ContainerStore.on(ContainerStore.CLIENT_CONTAINER_EVENT, this.updateFromClient);
|
ContainerStore.on(ContainerStore.CLIENT_CONTAINER_EVENT, this.updateFromClient);
|
||||||
|
|
||||||
if (this.state.sorted.length) {
|
if (this.state.sorted.length) {
|
||||||
this.transitionTo('container', {name: this.state.sorted[0].Name});
|
this.transitionTo('containerHome', {name: this.state.sorted[0].Name});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
componentDidUnmount: function () {
|
componentDidUnmount: function () {
|
||||||
|
@ -34,7 +34,7 @@ var Containers = React.createClass({
|
||||||
});
|
});
|
||||||
if (status === 'destroy') {
|
if (status === 'destroy') {
|
||||||
if (this.state.sorted.length) {
|
if (this.state.sorted.length) {
|
||||||
this.transitionTo('container', {name: this.state.sorted[0].Name});
|
this.transitionTo('containerHome', {name: this.state.sorted[0].Name});
|
||||||
} else {
|
} else {
|
||||||
this.transitionTo('containers');
|
this.transitionTo('containers');
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ var Containers = React.createClass({
|
||||||
sorted: ContainerStore.sorted()
|
sorted: ContainerStore.sorted()
|
||||||
});
|
});
|
||||||
if (status === 'create') {
|
if (status === 'create') {
|
||||||
this.transitionTo('container', {name: name});
|
this.transitionTo('containerHome', {name: name});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleScroll: function (e) {
|
handleScroll: function (e) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ var NewContainer = React.createClass({
|
||||||
getInitialState: function () {
|
getInitialState: function () {
|
||||||
return {
|
return {
|
||||||
query: '',
|
query: '',
|
||||||
results: ContainerStore.recommended(),
|
results: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
tags: {},
|
tags: {},
|
||||||
active: null,
|
active: null,
|
||||||
|
@ -24,6 +24,7 @@ var NewContainer = React.createClass({
|
||||||
});
|
});
|
||||||
this.refs.searchInput.getDOMNode().focus();
|
this.refs.searchInput.getDOMNode().focus();
|
||||||
ContainerStore.on(ContainerStore.CLIENT_RECOMMENDED_EVENT, this.update);
|
ContainerStore.on(ContainerStore.CLIENT_RECOMMENDED_EVENT, this.update);
|
||||||
|
this.update();
|
||||||
},
|
},
|
||||||
update: function () {
|
update: function () {
|
||||||
if (!this.state.query.length) {
|
if (!this.state.query.length) {
|
||||||
|
@ -105,8 +106,10 @@ var NewContainer = React.createClass({
|
||||||
render: function () {
|
render: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
var title = this.state.query ? 'Results' : 'Recommended';
|
var title = this.state.query ? 'Results' : 'Recommended';
|
||||||
var data = this.state.results.slice(0, 6);
|
var data = [];
|
||||||
|
if (this.state.results) {
|
||||||
|
data = this.state.results.slice(0, 6);
|
||||||
|
}
|
||||||
var results;
|
var results;
|
||||||
if (data.length) {
|
if (data.length) {
|
||||||
var items = data.map(function (r) {
|
var items = data.map(function (r) {
|
||||||
|
@ -173,11 +176,22 @@ var NewContainer = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
results = (
|
if (this.state.results.length === 0 && this.state.query === '') {
|
||||||
<div className="no-results">
|
results = (
|
||||||
<Radial spin="true" progress={90}/>
|
<div className="no-results">
|
||||||
</div>
|
<div className="loader">
|
||||||
);
|
<h2>Loading Images</h2>
|
||||||
|
<Radial spin="true" progress={90} thick={true} transparent={true} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
results = (
|
||||||
|
<div className="no-results">
|
||||||
|
<h1>Cannot find a matching image.</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var loadingClasses = React.addons.classSet({
|
var loadingClasses = React.addons.classSet({
|
||||||
hidden: !this.state.loading,
|
hidden: !this.state.loading,
|
||||||
|
@ -198,7 +212,7 @@ var NewContainer = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
<div className="search">
|
<div className="search">
|
||||||
<div className="search-bar">
|
<div className="search-bar">
|
||||||
<input type="search" ref="searchInput" className="form-control" placeholder="Find an existing image" onChange={this.handleChange}/>
|
<input type="search" ref="searchInput" className="form-control" placeholder="Find an image from Docker Hub" onChange={this.handleChange}/>
|
||||||
<div className={magnifierClasses}></div>
|
<div className={magnifierClasses}></div>
|
||||||
<RetinaImage className={loadingClasses} src="loading.png"/>
|
<RetinaImage className={loadingClasses} src="loading.png"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,7 +15,8 @@ var Radial = React.createClass({
|
||||||
'radial-spinner': this.props.spin,
|
'radial-spinner': this.props.spin,
|
||||||
'radial-negative': this.props.error,
|
'radial-negative': this.props.error,
|
||||||
'radial-thick': this.props.thick || false,
|
'radial-thick': this.props.thick || false,
|
||||||
'radial-gray': this.props.gray || false
|
'radial-gray': this.props.gray || false,
|
||||||
|
'radial-transparent': this.props.transparent || false
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div className={classes} data-progress={this.props.progress}>
|
<div className={classes} data-progress={this.props.progress}>
|
||||||
|
|
|
@ -2,6 +2,12 @@ var React = require('react/addons');
|
||||||
var Setup = require('./Setup.react');
|
var Setup = require('./Setup.react');
|
||||||
var Containers = require('./Containers.react');
|
var Containers = require('./Containers.react');
|
||||||
var ContainerDetails = require('./ContainerDetails.react');
|
var ContainerDetails = require('./ContainerDetails.react');
|
||||||
|
var ContainerHome = require('./ContainerHome.react');
|
||||||
|
var ContainerLogs = require('./ContainerLogs.react');
|
||||||
|
var ContainerSettings = require('./ContainerSettings.react');
|
||||||
|
var ContainerSettingsGeneral = require('./ContainerSettingsGeneral.react');
|
||||||
|
var ContainerSettingsPorts = require('./ContainerSettingsPorts.react');
|
||||||
|
var ContainerSettingsVolumes = require('./ContainerSettingsVolumes.react');
|
||||||
var Preferences = require('./Preferences.react');
|
var Preferences = require('./Preferences.react');
|
||||||
var NewContainer = require('./NewContainer.react');
|
var NewContainer = require('./NewContainer.react');
|
||||||
var Router = require('react-router');
|
var Router = require('react-router');
|
||||||
|
@ -21,7 +27,15 @@ var App = React.createClass({
|
||||||
var routes = (
|
var routes = (
|
||||||
<Route name="app" path="/" handler={App}>
|
<Route name="app" path="/" handler={App}>
|
||||||
<Route name="containers" handler={Containers}>
|
<Route name="containers" handler={Containers}>
|
||||||
<Route name="container" path="/containers/:name" handler={ContainerDetails}/>
|
<Route name="containerDetail" 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}>
|
||||||
|
<Route name="containerSettingsGeneral" path="/containers/:name/settings/general" handler={ContainerSettingsGeneral}/>
|
||||||
|
<Route name="containerSettingsPorts" path="/containers/:name/settings/ports" handler={ContainerSettingsPorts}/>
|
||||||
|
<Route name="containerSettingsVolumes" path="/containers/:name/settings/volumes" handler={ContainerSettingsVolumes}/>
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
<Route name="preferences" path="/preferences" handler={Preferences}/>
|
<Route name="preferences" path="/preferences" handler={Preferences}/>
|
||||||
<DefaultRoute name="new" handler={NewContainer}/>
|
<DefaultRoute name="new" handler={NewContainer}/>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
.image-item {
|
.image-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 320px;
|
width: 320px;
|
||||||
height: 170px;
|
height: 166px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid @gray-lightest;
|
border: 1px solid @gray-lightest;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
@ -50,6 +50,10 @@
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: @gray-darkest;
|
color: @gray-darkest;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
width: 190px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
img {
|
img {
|
||||||
margin-right: 7px;
|
margin-right: 7px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -59,7 +63,7 @@
|
||||||
.description {
|
.description {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: @gray-normal;
|
color: @gray-normal;
|
||||||
height: 70px;
|
height: 65px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
|
@ -136,6 +140,21 @@
|
||||||
flex: 1 auto;
|
flex: 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
.loader {
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: -20%;
|
||||||
|
text-align: center;
|
||||||
|
width: 300px;
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: @gray-lightest;
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: -20%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.new-container-header {
|
.new-container-header {
|
||||||
|
@ -253,7 +272,7 @@
|
||||||
color: @brand-action;
|
color: @brand-action;
|
||||||
transition: all 0.25s;
|
transition: all 0.25s;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: darken(@brand-action, 10%);
|
color: darken(@brand-action, 15%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,6 +325,8 @@
|
||||||
.btn-delete {
|
.btn-delete {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: white;
|
color: white;
|
||||||
|
position: relative;
|
||||||
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
.state-new {
|
.state-new {
|
||||||
.at2x('container-white.png', 20px, 20px);
|
.at2x('container-white.png', 20px, 20px);
|
||||||
|
@ -353,6 +374,7 @@
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
.name {
|
.name {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
max-width: 140px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -364,6 +386,7 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
max-width: 140px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
@ -379,6 +402,8 @@
|
||||||
.btn-delete {
|
.btn-delete {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: @gray-lighter;
|
color: @gray-lighter;
|
||||||
|
position: relative;
|
||||||
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,6 +546,9 @@
|
||||||
margin-top: -12px;
|
margin-top: -12px;
|
||||||
.action {
|
.action {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
.action-icon {
|
.action-icon {
|
||||||
color: @gray-normal;
|
color: @gray-normal;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
|
@ -564,6 +592,9 @@
|
||||||
color: white;
|
color: white;
|
||||||
background-image: linear-gradient(-180deg, #24B8EB 4%, #24A3EB 100%);
|
background-image: linear-gradient(-180deg, #24B8EB 4%, #24A3EB 100%);
|
||||||
}
|
}
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -657,129 +688,155 @@
|
||||||
.left {
|
.left {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.web-preview {
|
margin-right: 30px;
|
||||||
margin-right: 30px;
|
|
||||||
.subtext {
|
|
||||||
text-align: right;
|
|
||||||
color: @gray-lightest;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
.widget {
|
|
||||||
background-color: white;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid @gray-lightest;
|
|
||||||
position: relative;
|
|
||||||
iframe {
|
|
||||||
border: 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
/*width: 100%;
|
|
||||||
height: 100%;*/
|
|
||||||
position: relative;
|
|
||||||
top: -50%;
|
|
||||||
left: -50%;
|
|
||||||
width: 200%;
|
|
||||||
height: 200%;
|
|
||||||
transform: scale(0.5);
|
|
||||||
}
|
|
||||||
.iframe-overlay {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 100;
|
|
||||||
color: transparent;
|
|
||||||
transition: all 0.25s;
|
|
||||||
.icon {
|
|
||||||
margin-top: 40%;
|
|
||||||
display: block;
|
|
||||||
font-size: 60px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.text {
|
|
||||||
font-size: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
color: white;
|
|
||||||
background-color: @gray-darkest;
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.right {
|
.right {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.mini-logs {
|
}
|
||||||
margin-bottom: 50px;
|
.web-preview {
|
||||||
.widget {
|
margin-bottom: 50px;
|
||||||
position: relative;
|
.subtext {
|
||||||
border-radius: 4px;
|
text-align: right;
|
||||||
border: 1px solid @gray-lightest;
|
color: @gray-lightest;
|
||||||
background-color: @gray-darkest;
|
margin-top: 2px;
|
||||||
color: @gray-lightest;
|
transition: all 0.25s;
|
||||||
height: 100%;
|
&:hover {
|
||||||
|
color: darken(@gray-lightest, 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.widget {
|
||||||
|
background-color: white;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid @gray-lightest;
|
||||||
|
position: relative;
|
||||||
|
p {
|
||||||
|
font-size: 13px;
|
||||||
|
color: @gray-normal;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
overflow: hidden;
|
padding-bottom: 0px;
|
||||||
|
}
|
||||||
|
.ip-port {
|
||||||
|
padding: 20px;
|
||||||
|
padding-top: 5px;
|
||||||
|
color: @gray-darkest;
|
||||||
font-family: Menlo;
|
font-family: Menlo;
|
||||||
font-size: 8px;
|
-webkit-user-select: text;
|
||||||
white-space: pre-wrap;
|
}
|
||||||
p {
|
iframe {
|
||||||
margin-bottom: 0px;
|
border: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
transform: scale(0.5);
|
||||||
|
}
|
||||||
|
.iframe-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 100;
|
||||||
|
color: transparent;
|
||||||
|
transition: all 0.25s;
|
||||||
|
.icon {
|
||||||
|
margin-top: 40%;
|
||||||
|
display: block;
|
||||||
|
font-size: 60px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
.mini-logs-overlay {
|
.text {
|
||||||
position: absolute;
|
font-size: 20px;
|
||||||
top: 0;
|
text-align: center;
|
||||||
left: 0;
|
}
|
||||||
width: 100%;
|
&:hover {
|
||||||
height: 100%;
|
color: white;
|
||||||
z-index: 100;
|
background-color: @gray-darkest;
|
||||||
color: transparent;
|
opacity: 0.75;
|
||||||
transition: all 0.25s;
|
|
||||||
.icon {
|
|
||||||
margin-top: 25%;
|
|
||||||
display: block;
|
|
||||||
font-size: 60px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.text {
|
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
|
||||||
font-size: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
color: white;
|
|
||||||
background-color: @gray-darkest;
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.folders {
|
}
|
||||||
.subtext {
|
.mini-logs {
|
||||||
text-align: right;
|
margin-bottom: 50px;
|
||||||
color: @gray-lightest;
|
.widget {
|
||||||
margin-top: 2px;
|
position: relative;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid @gray-lightest;
|
||||||
|
background-color: @gray-darkest;
|
||||||
|
color: @gray-lightest;
|
||||||
|
height: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: Menlo;
|
||||||
|
font-size: 7px;
|
||||||
|
white-space: pre;
|
||||||
|
p {
|
||||||
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
.widget {
|
.mini-logs-overlay {
|
||||||
padding: 20px 10px;
|
position: absolute;
|
||||||
background-color: white;
|
top: 0;
|
||||||
border-radius: 4px;
|
left: 0;
|
||||||
border: 1px solid @gray-lightest;
|
width: 100%;
|
||||||
display: flex;
|
height: 100%;
|
||||||
.folder {
|
z-index: 100;
|
||||||
width: 100px;
|
color: transparent;
|
||||||
img {
|
transition: all 0.25s;
|
||||||
display: block;
|
.icon {
|
||||||
margin: 0 auto;
|
margin-top: 25%;
|
||||||
}
|
display: block;
|
||||||
.text {
|
font-size: 60px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.text {
|
||||||
|
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||||
|
font-size: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
color: white;
|
||||||
|
background-color: @gray-darkest;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.folders {
|
||||||
|
.subtext {
|
||||||
|
text-align: right;
|
||||||
|
color: @gray-lightest;
|
||||||
|
margin-top: 2px;
|
||||||
|
transition: all 0.25s;
|
||||||
|
&:hover {
|
||||||
|
color: darken(@gray-lightest, 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.widget {
|
||||||
|
padding: 10px 5px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid @gray-lightest;
|
||||||
|
display: flex;
|
||||||
|
.folder {
|
||||||
|
width: 110px;
|
||||||
|
padding: 5px;
|
||||||
|
&:hover {
|
||||||
|
background-color: #F9F9F9;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
margin-top: 4px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -799,108 +856,62 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.settings {
|
.settings {
|
||||||
padding: 18px 38px;
|
display: flex;
|
||||||
.settings-section {
|
flex: 1 auto;
|
||||||
margin-bottom: 40px;
|
flex-direction: row;
|
||||||
}
|
.settings-menu {
|
||||||
}
|
min-width: 160px;
|
||||||
.ports {
|
ul {
|
||||||
padding: 18px 38px;
|
position: fixed;
|
||||||
}
|
margin: 0;
|
||||||
.volumes {
|
|
||||||
padding: 18px 38px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table {
|
|
||||||
margin-bottom: 0;
|
|
||||||
.icon-arrow-right {
|
|
||||||
color: #aaa;
|
|
||||||
margin: 2px 9px 0;
|
|
||||||
flex: 0 auto;
|
|
||||||
min-width: 13px;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
min-width: 22px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
.table-labels {
|
|
||||||
margin-top: 20px;
|
|
||||||
flex: 1 auto;
|
|
||||||
display: flex;
|
|
||||||
font-size: 12px;
|
|
||||||
color: @gray-lightest;
|
|
||||||
.label-left {
|
|
||||||
flex: 0 auto;
|
|
||||||
min-width: 80px;
|
|
||||||
margin-right: 30px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.label-right {
|
|
||||||
flex: 1 auto;
|
|
||||||
display: inline-block;
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.table-values {
|
|
||||||
flex: 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
margin: 8px 0;
|
|
||||||
.value-left {
|
|
||||||
text-align: right;
|
|
||||||
min-width: 80px;
|
|
||||||
flex: 0 auto;
|
|
||||||
}
|
|
||||||
.value-right {
|
|
||||||
flex: 1 auto;
|
|
||||||
-webkit-user-select: text;
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.table-new {
|
|
||||||
margin-top: 10px;
|
|
||||||
flex: 1 auto;
|
|
||||||
display: flex;
|
|
||||||
input {
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-weight: 400;
|
padding-top: 14px;
|
||||||
}
|
|
||||||
input.new-left {
|
|
||||||
flex: 0 auto;
|
|
||||||
text-align: right;
|
|
||||||
min-width: 80px;
|
|
||||||
max-width: 80px;
|
|
||||||
}
|
|
||||||
.new-right-wrapper {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 auto;
|
flex-direction: column;
|
||||||
.new-right-placeholder {
|
|
||||||
position: absolute;
|
a {
|
||||||
top: 3px;
|
min-width: 160px;
|
||||||
left: 0;
|
margin-left: 12px;
|
||||||
font-weight: 400;
|
color: @gray-normal;
|
||||||
|
flex-shrink: 0;
|
||||||
|
cursor: default;
|
||||||
|
outline: none;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
&.active {
|
||||||
|
li {
|
||||||
|
color: white;
|
||||||
|
border-radius: 40px;
|
||||||
|
background-image: linear-gradient(-180deg, #24B8EB 4%, #24A3EB 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
li {
|
||||||
|
cursor: default;
|
||||||
|
border-radius: 40px;
|
||||||
|
background-color: #F9F9F9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input.new-right {
|
li {
|
||||||
flex: 1 auto;
|
vertical-align: middle;
|
||||||
height: 24px;
|
padding: 5px 12px;
|
||||||
position :relative;
|
display: flex;
|
||||||
padding-left: 107px;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.settings-panel {
|
||||||
&.volumes {
|
padding-left: 40px;
|
||||||
.label-left {
|
width: 100%;
|
||||||
min-width: 120px;
|
overflow-x: hidden;
|
||||||
}
|
.settings-section {
|
||||||
.value-left {
|
margin-bottom: 40px;
|
||||||
min-width: 120px;
|
|
||||||
}
|
|
||||||
.icon {
|
|
||||||
color: #aaa;
|
|
||||||
margin: 2px 9px 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -909,7 +920,16 @@
|
||||||
.container-name {
|
.container-name {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
input {
|
input {
|
||||||
width: 20%;
|
width: 40%;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-weight: 300;
|
||||||
|
margin-top: 5px;
|
||||||
|
color: @gray-lighter;
|
||||||
|
font-size: 12px;
|
||||||
|
strong {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -923,11 +943,11 @@
|
||||||
.label-key {
|
.label-key {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 30px;
|
margin-right: 30px;
|
||||||
width: 20%;
|
width: 30%;
|
||||||
}
|
}
|
||||||
.label-val {
|
.label-val {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 40%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.env-vars {
|
.env-vars {
|
||||||
|
@ -938,10 +958,109 @@
|
||||||
input {
|
input {
|
||||||
margin-right: 30px;
|
margin-right: 30px;
|
||||||
&.key {
|
&.key {
|
||||||
width: 20%;
|
width: 30%;
|
||||||
}
|
}
|
||||||
&.val {
|
&.val {
|
||||||
width: 40%;
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
margin-bottom: 0;
|
||||||
|
.icon-arrow-right {
|
||||||
|
color: #BBB;
|
||||||
|
font-size: 20px;
|
||||||
|
margin: 0px 10px;
|
||||||
|
flex: 0 auto;
|
||||||
|
min-width: 13px;
|
||||||
|
}
|
||||||
|
&.ports {
|
||||||
|
.table-labels {
|
||||||
|
margin-top: 20px;
|
||||||
|
flex: 1 auto;
|
||||||
|
display: flex;
|
||||||
|
font-size: 12px;
|
||||||
|
color: @gray-lightest;
|
||||||
|
.label-left {
|
||||||
|
flex: 0 auto;
|
||||||
|
min-width: 85px;
|
||||||
|
margin-right: 30px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.label-right {
|
||||||
|
flex: 1 auto;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 10px;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.table-values {
|
||||||
|
flex: 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 8px 0;
|
||||||
|
.value-left {
|
||||||
|
text-align: right;
|
||||||
|
min-width: 85px;
|
||||||
|
flex: 0 auto;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
.value-right {
|
||||||
|
flex: 1 auto;
|
||||||
|
-webkit-user-select: text;
|
||||||
|
max-width: 170px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-top: 1px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
input[type="checked"] {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.volumes {
|
||||||
|
.table-labels {
|
||||||
|
margin-top: 20px;
|
||||||
|
flex: 1 auto;
|
||||||
|
display: flex;
|
||||||
|
font-size: 12px;
|
||||||
|
color: @gray-lightest;
|
||||||
|
.label-left {
|
||||||
|
flex: 0 auto;
|
||||||
|
margin-right: 30px;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
.label-right {
|
||||||
|
flex: 1 auto;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 10px;
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.table-values {
|
||||||
|
flex: 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 8px 0;
|
||||||
|
.value-left {
|
||||||
|
width: 30%;
|
||||||
|
flex: 0 auto;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
.value-right {
|
||||||
|
flex: 1 auto;
|
||||||
|
-webkit-user-select: text;
|
||||||
|
width: 60%;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
flex: 0;
|
flex: 0;
|
||||||
min-height: 30px;
|
min-height: 40px;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
// border-bottom: 1px solid #efefef;
|
// border-bottom: 1px solid #efefef;
|
||||||
|
|
|
@ -90,24 +90,11 @@
|
||||||
.inset {
|
.inset {
|
||||||
width: @inset-size;
|
width: @inset-size;
|
||||||
height: @inset-size;
|
height: @inset-size;
|
||||||
position: absolute;
|
|
||||||
margin-left: (@circle-size - @inset-size) / 2.0;
|
margin-left: (@circle-size - @inset-size) / 2.0;
|
||||||
margin-top: (@circle-size - @inset-size) / 2.0;
|
margin-top: (@circle-size - @inset-size) / 2.0;
|
||||||
|
|
||||||
background-color: @inset-color;
|
|
||||||
border-radius: 100%;
|
|
||||||
.percentage {
|
.percentage {
|
||||||
width: @percentage-text-width;
|
|
||||||
position: absolute;
|
|
||||||
top: (@inset-size - @percentage-font-size) / 2.0;
|
top: (@inset-size - @percentage-font-size) / 2.0;
|
||||||
left: (@inset-size - @percentage-text-width) / 2.0;
|
left: (@inset-size - @percentage-text-width) / 2.0;
|
||||||
|
|
||||||
line-height: 1;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
color: @brand-primary;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: @percentage-font-size;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,6 +103,14 @@
|
||||||
background: #EEE;
|
background: #EEE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.radial-transparent {
|
||||||
|
@inset-color: #F9F9F9;
|
||||||
|
background: #F9F9F9;
|
||||||
|
.inset {
|
||||||
|
background-color: @inset-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@i: 0;
|
@i: 0;
|
||||||
@increment: 180deg / 100;
|
@increment: 180deg / 100;
|
||||||
.loop (@i) when (@i <= 100) {
|
.loop (@i) when (@i <= 100) {
|
||||||
|
|
|
@ -68,8 +68,8 @@ input[type="text"] {
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
border-color: darken(@btn-color, 10%);
|
border-color: darken(@btn-color, 15%);
|
||||||
color: darken(@btn-color, 10%);
|
color: darken(@btn-color, 15%);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
background: none;
|
background: none;
|
||||||
|
@ -77,8 +77,8 @@ input[type="text"] {
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: lighten(@btn-color, 45%);
|
background-color: lighten(@btn-color, 45%);
|
||||||
border-color: darken(@btn-color, 10%);
|
border-color: darken(@btn-color, 15%);
|
||||||
color: darken(@btn-color, 10%);
|
color: darken(@btn-color, 15%);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,11 +131,13 @@ input[type="text"] {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
padding: 4px 14px 4px 14px;
|
padding: 5px 14px 5px 14px;
|
||||||
height: 28px;
|
height: 30px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
&.small {
|
&.small {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 3px 8px 3px 8px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
.icon {
|
.icon {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
@ -179,7 +181,7 @@ input[type="text"] {
|
||||||
padding: 6px 7px 6px 7px;
|
padding: 6px 7px 6px 7px;
|
||||||
&.small {
|
&.small {
|
||||||
width: 22px;
|
width: 22px;
|
||||||
padding: 2px 5px 3px 5px;
|
padding: 4px 5px 4px 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue