How to build an industrial dashboard with Vue and Flask

Based on contributions by Boaz.

Build a browser-based dashboard for a robot cell or automated system when a terminal is no longer good enough — for example to show camera feeds, joint angles, and start/stop controls to an operator. This guide covers the architecture and a minimal working example using Vue 3 and Flask.

Example dashboard:

What you need

  • Python 3 with Flask and flask-cors.
  • Node.js and a Vue 3 project (created with, for example, npm create vue@latest).
  • Basic familiarity with Python and JavaScript. This guide assumes you already have a working Vue project — if you’re new to Vue, follow a beginner Vue tutorial first, since it is a large topic on its own.
  • If you’re controlling a robot from the backend, the relevant robot library (e.g. ur_rtde for Universal Robots).

Background: the three layers

A dashboard like this has three layers:

  1. Backend (Python + Flask) — the “brain”. Talks to the robot, e.g. through ur_rtde.
  2. API (REST) — the “bridge”. Lets the frontend send commands (like START/STOP) to the Python backend.
  3. Frontend (Vue.js) — the “face”. A UI showing camera feeds, joint angles, and controls.

Steps

1. Install dependencies

Backend (Python):

pip install flask flask-cors

flask-cors is required — without it, the browser blocks requests from the Vue frontend to the Flask backend for security reasons (the CORS policy), since they run on different ports/origins during development.

Frontend (inside your Vue project folder):

npm install axios       # for making API requests
npm install three       # for a 3D robot simulation, if you want one
npm install urdf-loader # to load a 3D robot model, if you want one

axios is the only one you need for a basic dashboard; three and urdf-loader are only needed if you plan to render a 3D model of the robot.

2. Build the backend (server.py)

The Flask server exposes REST endpoints that the dashboard calls. Keep it responsive — don’t block it while the robot is moving (see How to use multiprocessing in Python to run camera, detection, and robot loops in parallel for how to run robot control in a separate process so the API keeps responding).

from flask import Flask, jsonify
from flask_cors import CORS

app = Flask(__name__)
CORS(app)  # allow requests from the Vue frontend


@app.route("/api/status", methods=["GET"])
def get_status():
    return jsonify({"state": "IDLE", "connected": True})


if __name__ == "__main__":
    app.run(debug=True, port=5000)

Keep your robot control code (e.g. RTDE calls) in a separate module, such as robot_control.py, and import it into server.py. Don’t mix hardware logic with the web server code — it makes both easier to test and debug.

3. Build the frontend (.vue components)

A Vue single-file component has three parts: a <template> (HTML structure), a <script setup> (logic and reactive data), and a <style> (CSS).

Example: a production control card with a start button that calls the backend.

<template>
  <div class="card">
    <h3>Production Control</h3>
    <button @click="startRobot" :class="{ active: isRunning }">START</button>
    <p>Current state: {{ robotState }}</p>
  </div>
</template>

<script setup>
import { ref } from "vue";

const robotState = ref("STOPPED");
const isRunning = ref(false);

const startRobot = async () => {
  const response = await fetch("http://localhost:5000/api/program/run", { method: "POST" });
  if (response.ok) {
    isRunning.value = true;
    robotState.value = "RUNNING";
  }
};
</script>

<style scoped>
.card {
  background: white;
  padding: 20px;
  border-radius: 12px;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
button {
  background: #2ecc71;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 8px;
}
.active {
  background: #27ae60;
  border: 2px solid white;
}
</style>

This example calls /api/program/run directly — add that route on the Flask side the same way as /api/status in step 2.

4. Poll the backend for live data

For values that change continuously (joint positions, camera frames, sensor readings), poll the backend on an interval from Vue’s onMounted hook, for example every 100 ms:

import { onMounted, onUnmounted, ref } from "vue";
import axios from "axios";

const status = ref(null);
let pollTimer = null;

onMounted(() => {
  pollTimer = setInterval(async () => {
    const response = await axios.get("http://localhost:5000/api/status");
    status.value = response.data;
  }, 100);
});

onUnmounted(() => {
  clearInterval(pollTimer);
});

Always clear the interval in onUnmounted — otherwise it keeps polling after the component is gone.

Common mistakes

  • Skipping flask-cors. Requests from the Vue dev server (a different port, e.g. 5173) to Flask (5000) will fail silently in the browser console with a CORS error.
  • Blocking the Flask thread with slow robot calls. If a route waits several seconds for a robot move to finish, the whole API becomes unresponsive to other requests in the meantime. Run robot control in a separate process or thread and have the route just read/write shared state.
  • Mixing hardware code into the Flask routes. Keep robot/PLC logic in its own module so you can test and reuse it outside the web server.

Tips

  • Use Flexbox or CSS Grid so a camera feed can stay large on one side while controls stay accessible on the other, regardless of window size.
  • Vue is a large framework — following a general Vue tutorial alongside this guide will save time if you haven’t used it before.

Related


Rewritten and consolidated (Sept 2026) from the original student how-to’s: How to create an industrial dashboard with VUE.