Merge "Use xterm.js for live log streaming"

This commit is contained in:
Zuul 2019-04-02 16:04:58 +00:00 committed by Gerrit Code Review
commit 8c4a14675b
4 changed files with 50 additions and 87 deletions

View File

@ -23,7 +23,8 @@
"react-scripts": "1.1.4",
"redux": "<4.0.0",
"redux-thunk": "^2.3.0",
"sockette": "^2.0.0"
"sockette": "^2.0.0",
"xterm": "^3.12.0"
},
"devDependencies": {
"eslint": "^5.3.0",

View File

@ -107,30 +107,6 @@ a.refresh {
animation: progress-bar-stripes 1s linear infinite;
}
/* Stream page */
#zuulstreamoverlay {
float: right;
position: fixed;
top: 70px;
right: 5px;
background-color: white;
padding: 2px 0px 0px 2px;
color: black;
}
pre#zuulstreamcontent {
font-family: monospace;
white-space: pre;
margin: 0px 10px;
background-color: black;
color: lightgrey;
border: none;
}
p.zuulstreamline {
margin: 0px 0px;
line-height: 1.4;
}
/* Job Tree View group gap */
div.tree-view-container ul.list-group {
margin: 0px 0px;

View File

@ -16,11 +16,17 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Checkbox, Form, FormGroup } from 'patternfly-react'
import Sockette from 'sockette'
import 'xterm/dist/xterm.css'
import { Terminal } from 'xterm'
import * as fit from 'xterm/lib/addons/fit/fit'
import * as weblinks from 'xterm/lib/addons/webLinks/webLinks'
import { getStreamUrl } from '../api'
Terminal.applyAddon(fit)
Terminal.applyAddon(weblinks)
class StreamPage extends React.Component {
static propTypes = {
@ -29,10 +35,6 @@ class StreamPage extends React.Component {
tenant: PropTypes.object
}
state = {
autoscroll: true,
}
constructor() {
super()
this.receiveBuffer = ''
@ -40,59 +42,33 @@ class StreamPage extends React.Component {
this.lines = []
}
refreshLoop = () => {
if (this.displayRef.current) {
let newLine = false
this.lines.forEach(line => {
newLine = true
this.displayRef.current.appendChild(line)
})
this.lines = []
if (newLine) {
const { autoscroll } = this.state
if (autoscroll) {
this.messagesEnd.scrollIntoView({ behavior: 'instant' })
}
}
}
this.timer = setTimeout(this.refreshLoop, 250)
}
componentWillUnmount () {
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
if (this.ws) {
console.log('Remove ws')
this.ws.close()
}
}
onLine = (line) => {
// Create dom elements
const lineDom = document.createElement('p')
lineDom.className = 'zuulstreamline'
lineDom.appendChild(document.createTextNode(line))
this.lines.push(lineDom)
onMessage = (message) => {
this.term.write(message)
}
onMessage = (message) => {
this.receiveBuffer += message
const lines = this.receiveBuffer.split('\n')
const lastLine = lines.slice(-1)[0]
// Append all completed lines
lines.slice(0, -1).forEach(line => {
this.onLine(line)
})
// Check if last chunk is completed
if (lastLine && this.receiveBuffer.slice(-1) === '\n') {
this.onLine(lastLine)
this.receiveBuffer = ''
} else {
this.receiveBuffer = lastLine
onResize = () => {
// Note: We call proposeGeometry to get the number of cols and rows that
// fit into the parent element. However the number of rows is not detected
// correctly so we derive this directly from the window height.
const geometry = this.term.proposeGeometry()
if (geometry) {
const cellHeight = this.term._core.renderer.dimensions.actualCellHeight
const height = window.innerHeight - this.term.element.offsetTop - 10
const rows = Math.max(Math.floor(height / cellHeight), 10)
const cols = Math.max(geometry.cols, 10)
if (this.term.rows !== rows || this.term.cols !== cols) {
this.term.resize(cols, rows)
}
}
this.refreshLoop()
}
componentDidMount() {
@ -105,6 +81,19 @@ class StreamPage extends React.Component {
params.logfile = logfile
}
document.title = 'Zuul Stream | ' + params.uuid.slice(0, 7)
const term = new Terminal()
term.webLinksInit()
term.setOption('fontSize', 12)
term.setOption('scrollback', 1000000)
term.setOption('disableStdin', true)
term.setOption('convertEol', true)
term.attachCustomKeyEventHandler(function () {return false})
term.open(this.terminal)
this.ws = new Sockette(getStreamUrl(this.props.tenant.apiPrefix), {
timeout: 5e3,
maxAttempts: 3,
@ -129,26 +118,18 @@ class StreamPage extends React.Component {
console.log('onerror:', e)
}
})
}
handleCheckBox = (e) => {
this.setState({autoscroll: e.target.checked})
this.term = term
term.element.style.padding = '5px'
this.onResize()
window.addEventListener('resize', this.onResize)
}
render () {
return (
<React.Fragment>
<Form inline id='zuulstreamoverlay'>
<FormGroup controlId='stream'>
<Checkbox
checked={this.state.autoscroll}
onChange={this.handleCheckBox}>
autoscroll
</Checkbox>
</FormGroup>
</Form>
<pre id='zuulstreamcontent' ref={this.displayRef} />
<div ref={(el) => { this.messagesEnd = el }} />
<div ref={ref => this.terminal = ref}/>
</React.Fragment>
)
}

View File

@ -7985,6 +7985,11 @@ xtend@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
xterm@^3.12.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.12.0.tgz#74cc54013140cf0fd38a05a0d5d49e013e8a53bd"
integrity sha512-U5w1NJdrqAtnNju4W05uOxLzNgMD1sk0AnIkZ//Wa7xRdQTi9Dl1qkPdAaxWJ1a7A8xzNM4ogrX/4oSVl15qOw==
y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"