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_rtdefor Universal Robots).
Background: the three layers
A dashboard like this has three layers:
- Backend (Python + Flask) — the “brain”. Talks to the robot, e.g. through
ur_rtde. - API (REST) — the “bridge”. Lets the frontend send commands (like START/STOP) to the Python backend.
- 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
- How to create a simple web interface for a system using Python and Flask
- How to use multiprocessing in Python to run camera, detection, and robot loops in parallel
Rewritten and consolidated (Sept 2026) from the original student how-to’s: How to create an industrial dashboard with VUE.
