Merge "Use xterm.js for live log streaming"
This commit is contained in:
commit
8c4a14675b
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue