Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stream shell output to web front end

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: 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.

like image 447
stdout Avatar asked Feb 16 '19 17:02

stdout


1 Answers

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:

  1. Convert ANSI strings to HTML and send them to clients
  2. Send the ANSI strings to the client and ask them to convert it

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>

like image 74
Guru Prasad Avatar answered Oct 24 '22 06:10

Guru Prasad