I'm looking to present the realtime output of a long running shell command running on a backend server including ANSI escape code rendering, much like how GitLab shows the output of a CI pipeline. For example:
Are there any existing libraries or frameworks that can provide this functionality? I would anticipate the frontend would retrieve the output from the backend via using REST calls in a loop, websockets, or similar. It looks like jQuery Terminal Emulator is close, but I am not looking to have an interactive terminal. The application's stack is currently using Django Rest Framework on the backend and Vue.js on the frontend.
This is a pretty broad question, but a fun one. I found it difficult to provide an answer to this purely in terms of code, but perhaps you will find my answer useful nonetheless.
There are a couple of ways of achieving this:
I opted for the second since you seem to be using Python in the backend, and I don't really know Python web programming to give you a full example. In this case, option 2 restricts the amount of code you will have to replicate in Python to get the functionality shown below.
You can find a full-fledged example with frontend and backend (NodeJS) code here. The backend is simply a websocket server that pipes output of a command to incoming connections.
For the output, I tried using apt update
and top
, but apt update
will not render color output when invoked from NodeJS and top
would complain about missing tty
. Instead, I decided to use a simple shell script that generates strings with random ANSI colors. Most of this logic was borrowed from this answer.
The frontend is an extremely simple Console
VueJS component which I've described below. All of the heavy-lifting is done by the ansi_up library and I simply use it's output directly as the HTML content of the component.
<template>
<div class="console" v-html="html">
</div>
</template>
<script>
import AnsiUp from 'ansi_up'
export default {
name: 'shell',
props: ['socket'],
data () {
return {
ansi: undefined,
content: ''
}
},
computed: {
html () {
// Ensures we have some semblance of lines
return this.ansi.ansi_to_html(this.content).replace(/\n/gm, '<br>')
}
},
watch: {
socket () {
this.socket.on('data', data => {
this.content += data
})
this.socket.send('ready')
}
},
beforeMount () {
this.ansi = new AnsiUp()
},
updated () {
// auto-scroll to the bottom when the DOM is updated
this.$el.scrollTop = this.$el.scrollHeight
}
}
</script>
<style lang="scss" scoped>
.console {
font-family: monospace;
text-align: left;
background-color: black;
color: #fff;
overflow-y: auto;
}
</style>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With