mirror of https://github.com/docker/docs.git
traffic lights
This commit is contained in:
parent
4c3d3ad08b
commit
42417c3523
|
@ -1,3 +1,4 @@
|
|||
var _ = require('underscore');
|
||||
var React = require('react');
|
||||
var Router = require('react-router');
|
||||
var Route = Router.Route;
|
||||
|
@ -6,9 +7,92 @@ var DefaultRoute = Router.DefaultRoute;
|
|||
var Link = Router.Link;
|
||||
var RouteHandler = Router.RouteHandler;
|
||||
|
||||
var Convert = require('ansi-to-html');
|
||||
var convert = new Convert();
|
||||
|
||||
var docker = require('./docker.js');
|
||||
|
||||
var Container = React.createClass({
|
||||
mixins: [Router.State],
|
||||
componentWillReceiveProps: function () {
|
||||
var self = this;
|
||||
var logs = [];
|
||||
var index = 0;
|
||||
docker.client().getContainer(this.getParams().Id).logs({
|
||||
follow: false,
|
||||
stdout: true,
|
||||
timestamps: true
|
||||
}, function (err, stream) {
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', function (buf) {
|
||||
// Every other message is a header
|
||||
if (index % 2 === 1) {
|
||||
var time = buf.substr(0,buf.indexOf(' '));
|
||||
var msg = buf.substr(buf.indexOf(' ')+1);
|
||||
logs.push(convert.toHtml(msg));
|
||||
}
|
||||
index += 1;
|
||||
});
|
||||
stream.on('end', function (buf) {
|
||||
self.setState({logs: logs});
|
||||
docker.client().getContainer(self.getParams().Id).logs({
|
||||
follow: true,
|
||||
stdout: true,
|
||||
timestamps: true,
|
||||
tail: 0
|
||||
}, function (err, stream) {
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', function (buf) {
|
||||
// Every other message is a header
|
||||
if (index % 2 === 1) {
|
||||
var time = buf.substr(0,buf.indexOf(' '));
|
||||
var msg = buf.substr(buf.indexOf(' ')+1);
|
||||
logs.push(convert.toHtml(msg));
|
||||
self.setState({logs: logs});
|
||||
}
|
||||
index += 1;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
return <p>Hello</p>;
|
||||
var self = this;
|
||||
|
||||
if (!this.state || !this.state.logs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var container = _.find(this.props.containers, function (container) {
|
||||
return container.Id === self.getParams().Id;
|
||||
});
|
||||
// console.log(container);
|
||||
|
||||
if (!container || !this.state) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
var logs = this.state.logs.map(function (l, i) {
|
||||
return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>;
|
||||
});
|
||||
|
||||
var state;
|
||||
if (container.State.Running) {
|
||||
state = <h2 className="status">running</h2>;
|
||||
} else if (container.State.Restarting) {
|
||||
state = <h2 className="status">restarting</h2>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="details-header">
|
||||
<h1>{container.Name.replace('/', '')}</h1>{state}
|
||||
</div>
|
||||
<div className="logs">
|
||||
{logs}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -7,16 +7,37 @@ var Link = Router.Link;
|
|||
var RouteHandler = Router.RouteHandler;
|
||||
var Navigation= Router.Navigation;
|
||||
|
||||
var Header = require('./Header.react.js');
|
||||
|
||||
var async = require('async');
|
||||
var docker = require('./docker.js');
|
||||
|
||||
|
||||
var ContainerList = React.createClass({
|
||||
render: function () {
|
||||
var containers = this.props.containers.map(function (container) {
|
||||
return <li key={container.Id}><Link to="container" params={container}>{container.Name.replace('/', '')}</Link></li>
|
||||
var state;
|
||||
if (container.State.Running) {
|
||||
state = <span className="status">running</span>;
|
||||
} else if (container.State.Restarting) {
|
||||
state = <span className="status">restarting</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link key={container.Id} to="container" params={{Id: container.Id, container: container}}>
|
||||
<li>
|
||||
<div className="name">
|
||||
{container.Name.replace('/', '')}
|
||||
</div>
|
||||
<div className="image">
|
||||
{state} - {container.Config.Image}
|
||||
</div>
|
||||
</li>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<ul>
|
||||
<ul className="container-list">
|
||||
{containers}
|
||||
</ul>
|
||||
);
|
||||
|
@ -26,7 +47,7 @@ var ContainerList = React.createClass({
|
|||
var Containers = React.createClass({
|
||||
mixins: [Navigation],
|
||||
getInitialState: function() {
|
||||
return {containers: []};
|
||||
return {containers: [], index: null};
|
||||
},
|
||||
update: function () {
|
||||
var self = this;
|
||||
|
@ -37,7 +58,7 @@ var Containers = React.createClass({
|
|||
});
|
||||
}, function (err, results) {
|
||||
if (results.length > 0) {
|
||||
self.transitionTo('container', {Id: results[0].Id})
|
||||
self.transitionTo('container', {Id: results[0].Id, container: results[0]});
|
||||
}
|
||||
self.setState({containers: results});
|
||||
});
|
||||
|
@ -58,9 +79,16 @@ var Containers = React.createClass({
|
|||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<div className="containers">
|
||||
<Header/>
|
||||
<div className="containers-body">
|
||||
<div className="sidebar">
|
||||
<ContainerList containers={this.state.containers}/>
|
||||
<RouteHandler/>
|
||||
</div>
|
||||
<div className="details container">
|
||||
<RouteHandler containers={this.state.containers}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
var React = require('react/addons');
|
||||
var remote = require('remote');
|
||||
|
||||
var Header = React.createClass({
|
||||
handleClose: function () {
|
||||
remote.getCurrentWindow().hide();
|
||||
},
|
||||
handleMinimize: function () {
|
||||
remote.getCurrentWindow().minimize();
|
||||
},
|
||||
handleMaximize: function () {
|
||||
remote.getCurrentWindow().setFullScreen(!remote.getCurrentWindow().isFullScreen());
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className="header">
|
||||
<div className="buttons">
|
||||
<div className="button button-close" onClick={this.handleClose}></div>
|
||||
<div className="button button-minimize" onClick={this.handleMinimize}></div>
|
||||
<div className="button button-maximize" onClick={this.handleMaximize}></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Header;
|
Binary file not shown.
After Width: | Height: | Size: 243 B |
Binary file not shown.
After Width: | Height: | Size: 355 B |
Binary file not shown.
After Width: | Height: | Size: 239 B |
Binary file not shown.
After Width: | Height: | Size: 321 B |
Binary file not shown.
After Width: | Height: | Size: 106 B |
Binary file not shown.
After Width: | Height: | Size: 115 B |
|
@ -0,0 +1,56 @@
|
|||
@font-face {
|
||||
font-family: 'Clear';
|
||||
src: url('clearsans-regular-webfont.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Clear Sans';
|
||||
src: url('clearsans-medium-webfont.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Clear Sans';
|
||||
src: url('clearsans-light-webfont.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Clear Sans';
|
||||
src: url('clearsans-thin-webfont.ttf') format('truetype');
|
||||
font-weight: 100;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Clear Sans';
|
||||
src: url('clearsans-mediumitalic-webfont.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Clear Sans';
|
||||
src: url('clearsans-italic-webfont.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Clear Sans';
|
||||
src: url('clearsans-bolditalic-webfont.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Clear Sans';
|
||||
src: url('clearsans-bold-webfont.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
}
|
|
@ -1,7 +1,179 @@
|
|||
@import "bootstrap/bootstrap.less";
|
||||
@import "clearsans.less";
|
||||
@import "retina.less";
|
||||
@import "setup.less";
|
||||
@import "radial.less";
|
||||
|
||||
body {
|
||||
background: white;
|
||||
.header {
|
||||
height: 48px;
|
||||
border-bottom: 1px solid #eee;
|
||||
-webkit-app-region: drag;
|
||||
-webkit-user-select: none;
|
||||
|
||||
.buttons {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 16px;
|
||||
left: 20px;
|
||||
|
||||
&:hover {
|
||||
.button-minimize {
|
||||
.at2x('minimize.png', 10px, 10px);
|
||||
}
|
||||
.button-close {
|
||||
.at2x('close.png', 10px, 10px);
|
||||
}
|
||||
.button-maximize {
|
||||
.at2x('maximize.png', 10px, 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
background: white;
|
||||
margin-right: 9px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
border: 1px solid #CACDD0;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0px 1px 1px 0px rgba(234,234,234,0.50);
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 1px 1px 0px rgba(195,198,201,0.50);
|
||||
}
|
||||
|
||||
&:active {
|
||||
-webkit-filter: brightness(92%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
min-width: 256px;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
|
||||
&:hover {
|
||||
background: #efefef;
|
||||
}
|
||||
|
||||
padding: 14px 24px;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
.name {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.image {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
margin-top: 2px;
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.containers {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
|
||||
.containers-body {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
.sidebar {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
min-width: 256px;
|
||||
// border-right: 1px solid #eee;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
|
||||
.status {
|
||||
font-size: 12px;
|
||||
font-variant: small-caps;
|
||||
color: @brand-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.details-header {
|
||||
padding: 14px 45px;
|
||||
background: white;
|
||||
width: 100%;
|
||||
h1 {
|
||||
display: inline-block;
|
||||
font-size: 24px;
|
||||
}
|
||||
h2 {
|
||||
margin-left: 18px;
|
||||
font-size: 14px;
|
||||
display: inline-block;
|
||||
font-variant: small-caps;
|
||||
|
||||
&.status {
|
||||
color: @brand-success;
|
||||
}
|
||||
|
||||
&.image {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logs {
|
||||
overflow: auto;
|
||||
font-family: Menlo;
|
||||
font-size: 12px;
|
||||
padding: 44px 45px;
|
||||
p {
|
||||
margin: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
-webkit-font-smoothing: subpixel-antialiased;
|
||||
font-family: 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Imulus, LLC, Ben Atkin, and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
// retina.less
|
||||
// A helper mixin for applying high-resolution background images (http://www.retinajs.com)
|
||||
|
||||
@highdpi: ~"(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-resolution: 1.5dppx)";
|
||||
|
||||
.at2x(@path, @w: auto, @h: auto) {
|
||||
background-image: url(@path);
|
||||
@at2x_path: ~`@{path}.replace(/\.\w+$/, function(match) { return "@2x" + match; })`;
|
||||
|
||||
@media @highdpi {
|
||||
background-image: url("@{at2x_path}");
|
||||
background-size: @w @h;
|
||||
}
|
||||
}
|
|
@ -28,11 +28,10 @@ app.on('ready', function() {
|
|||
var windowOptions = {
|
||||
width: 1200,
|
||||
height: 800,
|
||||
'min-width': 1080,
|
||||
'min-height': 560,
|
||||
resizable: true,
|
||||
frame: true,
|
||||
'web-preferences': {
|
||||
'web-security': false
|
||||
}
|
||||
frame: false
|
||||
};
|
||||
mainWindow = new BrowserWindow(windowOptions);
|
||||
mainWindow.hide();
|
||||
|
@ -48,7 +47,7 @@ app.on('ready', function() {
|
|||
var saveVMOnQuit = true;
|
||||
app.on('will-quit', function (e) {
|
||||
if (saveVMOnQuit) {
|
||||
exec('VBoxManage controlvm boot2docker-vm savestate', function (stderr, stdout, code) {});
|
||||
// exec('VBoxManage controlvm boot2docker-vm savestate', function (stderr, stdout, code) {});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
16
gulpfile.js
16
gulpfile.js
|
@ -114,9 +114,13 @@ gulp.task('images', function() {
|
|||
|
||||
gulp.task('styles', function () {
|
||||
return gulp.src('app/styles/main.less')
|
||||
.pipe(plumber())
|
||||
.pipe(plumber(function(error) {
|
||||
gutil.log(gutil.colors.red('Error (' + error.plugin + '): ' + error.message));
|
||||
// emit the end event, to properly end the task
|
||||
this.emit('end');
|
||||
}))
|
||||
.pipe(gulpif(options.dev, sourcemaps.init()))
|
||||
.pipe(less()).on('error', console.error.bind(console))
|
||||
.pipe(less())
|
||||
.pipe(gulpif(options.dev, sourcemaps.write()))
|
||||
.pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build'))
|
||||
.pipe(gulpif(!options.dev, cssmin()))
|
||||
|
@ -135,6 +139,10 @@ gulp.task('copy', function () {
|
|||
gulp.src('./app/index.html')
|
||||
.pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build'))
|
||||
.pipe(gulpif(options.dev && !options.test, livereload()));
|
||||
|
||||
gulp.src('./app/fonts/**')
|
||||
.pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build'))
|
||||
.pipe(gulpif(options.dev && !options.test, livereload()));
|
||||
});
|
||||
|
||||
gulp.task('dist', function (cb) {
|
||||
|
@ -206,8 +214,8 @@ gulp.task('test', ['download', 'copy', 'js', 'images', 'styles', 'specs'], funct
|
|||
|
||||
gulp.task('default', ['download', 'copy', 'js', 'images', 'styles'], function () {
|
||||
gulp.watch('./app/**/*.html', ['copy']);
|
||||
gulp.watch('./app/images/**', ['images']);
|
||||
gulp.watch('./app/styles/**', ['styles']);
|
||||
gulp.watch('./app/styles/**/*.less', ['styles']);
|
||||
gulp.watch('./app/images/**/*.png', ['images']);
|
||||
|
||||
livereload.listen();
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
"dockerode": "2.0.4",
|
||||
"exec": "0.1.2",
|
||||
"flux-react": "^2.6.1",
|
||||
"ftscroller": "^0.5.1",
|
||||
"iscroll": "^5.1.3",
|
||||
"leveldown": "^1.0.0",
|
||||
"levelup": "git+https://github.com/kitematic/node-levelup.git",
|
||||
"minimist": "^1.1.0",
|
||||
|
@ -37,8 +39,8 @@
|
|||
"react": "^0.12.1",
|
||||
"request": "2.42.0",
|
||||
"request-progress": "0.3.1",
|
||||
"tar": "0.1.20",
|
||||
"retina.js": "^1.1.0"
|
||||
"retina.js": "^1.1.0",
|
||||
"tar": "0.1.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^6.2.0",
|
||||
|
|
Loading…
Reference in New Issue