some terminal improvements

This commit is contained in:
Vladimir Polyakov 2015-11-18 23:14:46 +03:00
parent e1b1d5e688
commit 8c8b600ab8
9 changed files with 151 additions and 93 deletions

View File

@ -50,7 +50,7 @@ steps:
- cmd: cat 1.txt 2.txt
- shell: /bin/bash
cmd: >
for i in {1..10}; do
for i in {1..1000}; do
echo "tick $i";
sleep 0.3;
done;

8
db.js
View File

@ -94,6 +94,14 @@ exports.init = function(dbPath, params, callback) {
value: function(logLine) {
return _(logLine).pick('number', 'text');
}
},
{
key: {
buildId: 1
},
value: function(logLine) {
return _(logLine).pick('number', 'text');
}
}
],
withUniqueId: false

View File

@ -49,28 +49,27 @@ exports.init = function(app, callback) {
}
var buildDataResource = app.dataio.resource('build' + buildId);
buildDataResource.on('connection', function(client) {
var callback = this.async(),
buildLogPath = getBuildLogPath(buildId);
var stream = fs.createReadStream(buildLogPath, {
encoding: 'utf8'
});
stream
.on('readable', function() {
var data = stream.read();
while (data) {
client.emit('sync', 'data', {lines: [{text: data}]});
data = stream.read();
var callback = this.async();
Steppy(
function() {
db.logLines.find({
start: {buildId: buildId, numberStr: 0},
}, this.slot());
},
function(err, lines) {
client.emit('sync', 'data', {lines: lines});
this.pass(true);
},
function(err) {
if (err) {
logger.error(
'error during read log for "' + buildId + '":',
err.stack || err
);
}
})
.on('end', callback)
.on('error', function(err) {
logger.error(
'Error during read "' + buildLogPath + '":',
err.stack || err
);
});
callback();
}
);
});
buildDataResourcesHash[buildId] = buildDataResource;
};
@ -105,16 +104,16 @@ exports.init = function(app, callback) {
var buildLogLineNumbersHash = {};
distributor.on('buildData', function(build, data) {
if (!/\n$/.test(data)) {
data += '\n';
}
var lines = data.trim().split('\n'),
logLineNumber = buildLogLineNumbersHash[build.id] || 0;
if (buildLogLineNumbersHash[build.id]) {
buildLogLineNumbersHash[build.id]++;
} else {
buildLogLineNumbersHash[build.id] = 1;
}
var logLineNumber = buildLogLineNumbersHash[build.id];
lines = _(lines).map(function(line, index) {
return {
number: logLineNumber + index,
text: line
};
});
buildLogLineNumbersHash[build.id] = logLineNumber + lines.length;
var filePath = getBuildLogPath(build.id);
@ -134,24 +133,23 @@ exports.init = function(app, callback) {
app.dataio.resource('build' + build.id).clientEmitSync(
'data',
{lines: [{number: logLineNumber, text: data}]}
{lines: lines}
);
// write build logs to db
db.logLines.put({
buildId: build.id,
number: logLineNumber,
text: data
}, function(err) {
if (err) {
logger.error(
'Error during write log line "' + logLineNumber +
'" for build "' + build.id + '":',
err.stack || err
);
}
});
_(lines).each(function(line) {
db.logLines.put(_({
buildId: build.id,
}).extend(line), function(err) {
if (err) {
logger.error(
'Error during write log line "' + logLineNumber +
'" for build "' + build.id + '":',
err.stack || err
);
}
});
})
});
callback(null, distributor);

View File

@ -1,25 +1,63 @@
.terminal {
box-sizing: border-box;
position: relative;
&_header {
}
&_code {
height: 420px;
padding: 8px 12px;
box-sizing: border-box;
clear: left;
height: 500px;
min-height: 42px;
padding: 15px 0;
color: #F1F1F1;
font-family: monospace;
font-size: 12px;
line-height: 19px;
white-space: pre-wrap;
word-wrap: break-word;
background-color: #2a2a2a;
counter-reset: line-numbering;
margin-top: 0;
overflow-y: scroll;
border: none;
color: white;
background-color: black;
line-height: 0.8;
font-size: 13px;
font-family: 'Ubuntu', sans-serif;
}
&_footer {
&_newline {
display: block;
margin: 0.8em 0;
white-space: pre;
&:first-child {
margin-top: 0;
}
}
}
}
.code-line {
position: relative;
padding: 0 15px 0 55px;
margin: 0;
min-height: 16px;
&_counter {
display: inline-block;
text-align: right;
min-width: 40px;
margin-left: -33px;
cursor: pointer;
text-decoration: none;
color: darken(@gray-lighter, 15%);
&:before {
content: counter(line-numbering);
counter-increment: line-numbering;
padding-right: 1em;
}
&:hover {
text-decoration: none;
color: @gray-lighter;
}
}
&_body {
display: inline-block;
}
&:hover {
background-color: #444;
}
}

View File

@ -1,2 +1,6 @@
.terminal
.terminal_code(ref="code", onScroll=this.onScroll)!= this.state.data
pre.terminal_code(ref="code")
each row, index in this.state.data
.code-line(key=index)
span.code-line_counter
.code-line_body!= row

View File

@ -10,48 +10,36 @@ define([
], function(_, React, Reflux, terminalStore, ansiUp, template) {
var Component = React.createClass({
mixins: [Reflux.ListenerMixin],
scrollOnData: true,
shouldScrollBottom: true,
ignoreScrollEvent: false,
componentDidMount: function() {
this.listenTo(terminalStore, this.updateItems);
},
ensureScrollPosition: function() {
if (this.scrollOnData) {
var codeNode = this.refs.code.getDOMNode();
this.ignoreScrollEvent = true;
codeNode.scrollTop = codeNode.scrollHeight - codeNode.offsetHeight;
}
},
onScroll: function() {
if (!this.ignoreScrollEvent) {
var codeNode = this.refs.code.getDOMNode();
if (codeNode.offsetHeight + codeNode.scrollTop >= codeNode.scrollHeight) {
this.scrollOnData = true;
} else {
this.scrollOnData = false;
}
}
this.ignoreScrollEvent = false;
},
prepareOutput: function(output) {
var text = output.replace(
/(.*)\n/gi,
'<span class="terminal_code_newline">$1</span>'
);
return ansiUp.ansi_to_html(text);
return output.map(function(row) {
return ansiUp.ansi_to_html(row.text);
});
},
componentWillUpdate: function() {
var node = this.refs.code.getDOMNode();
this.shouldScrollBottom = node.scrollTop + node.offsetHeight >= node.scrollHeight;
},
componentDidUpdate: function() {
if (this.shouldScrollBottom) {
var node = this.refs.code.getDOMNode();
node.scrollTop = node.scrollHeight;
}
},
updateItems: function(build) {
// listen just our console update
if (build.buildId === this.props.build) {
this.setState({data: this.prepareOutput(build.data)});
_.defer(this.ensureScrollPosition);
this.ensureScrollPosition();
}
},
render: template,
getInitialState: function() {
return {
data: ''
data: []
};
}
});

View File

@ -0,0 +1,3 @@
p 123
Terminal(lines=this.state.lines)

View File

@ -0,0 +1,19 @@
'use strict';
define([
'react',
'../terminal',
'templates/app/components/terminal/test/index'
], function(React, TerminalComponent, template) {
template = template.locals({
Terminal: TerminalComponent
});
return React.createClass({
getInitialState: function() {
return {
lines: [1, 2, 3]
};
},
render: template
});
});

View File

@ -16,7 +16,7 @@ define([
onReadTerminalOutput: function(build) {
var self = this,
output = '',
output = [],
resourceName = 'build' + build.id;
var connectToBuildDataResource = function() {
@ -29,7 +29,7 @@ define([
}
connect.resource(resourceName).subscribe('data', function(data) {
output += _(data.lines).pluck('text').join('');
output = output.concat(data.lines);
self.trigger({
buildId: build.id,