Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Capabilities2 UI
# Event Logger UI

A Node.js + React-based web application that connects to [`foxglove-rosbridge`](https://github.com/foxglove/ros-foxglove-bridge) and visualizes `/events` messages published in ROS2 using the CDR encoding format.

Expand All @@ -9,29 +9,39 @@ This app allows:
- REST API for historical data access


## 🚀 How to Run

### 1. Prerequisites
## Prerequisites

- Node.js ≥ 18.x
- MongoDB (running locally or via Atlas)
- ROS2 Capabilities2 system publishing to `/events` via [foxglove-bridge](https://github.com/foxglove/ros-foxglove-bridge)

### 2. Clone and Setup
## Clone and Setup

```bash
git clone https://github.com/CollaborativeRoboticsLab/capabilities2-ui.git
cd capabilities2-ui
git clone https://github.com/CollaborativeRoboticsLab/event_logger_ui.git
cd event_logger_ui
```

### 3. Backend
## Docker based Deployment

Make sure you have docker installed. Then,

```sh
docker compose up
```

## Pure Deployment

### Backend

If you want to modify or start the backend seperately, run following commands

```bash
cd backend
npm install

# Setup MongoDB URI
echo "MONGO_URI=mongodb://localhost:27017/capabilities2" > .env
echo "MONGO_URI=mongodb://localhost:27017/event_logger" > .env

# Start the backend (REST API + WebSocket + Foxglove client)
node server.js
Expand All @@ -52,7 +62,9 @@ node server.js
```


### 4. Frontend
### Frontend

If you want to modify or start the backend seperately, run following commands

```bash
cd ../frontend
Expand Down
9 changes: 9 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
.env
*.md
*.log
21 changes: 21 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Use a lightweight Node.js base image
FROM node:18-alpine

# Set working directory
WORKDIR /app

# Copy package files and install dependencies
COPY package.json package-lock.json ./
RUN npm ci

# Copy backend source code
COPY . .

# Set environment variables (override in production)
ENV PORT=5000

# Expose backend port
EXPOSE 5000

# Start server
CMD ["node", "server.js"]
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const TYPE_ENUM_MAP = {
5: "RUNNER_EVENT",
};

function decodeCapabilityEvent(data) {
function decodeEvent(data) {
const reader = new CdrReader(data);
try {
const header = {
Expand All @@ -36,13 +36,13 @@ function decodeCapabilityEvent(data) {
capability: reader.string(),
provider: reader.string(),
parameters: reader.string(),
};
}

const target = {
capability: reader.string(),
provider: reader.string(),
parameters: reader.string(),
};
}

const thread_id = reader.int8();

Expand All @@ -69,4 +69,4 @@ function decodeCapabilityEvent(data) {
}
}

module.exports = decodeCapabilityEvent;
module.exports = decodeEvent;
2 changes: 1 addition & 1 deletion backend/decoders/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
"/events": require("./capabilitiesEventDecoder"),
"/events": require("./EventDecoder"),

// future topics can be added like:
// "/sensor_data": require("./sensorDataDecoder"),
Expand Down
8 changes: 4 additions & 4 deletions backend/foxgloveClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ async function startFoxgloveClient(sessionId) {
console.log(`[FoxgloveClient] Starting client for session: ${sessionId}`);

const tryConnect = () => {
console.log("[FoxgloveClient] Attempting to connect to ws://0.0.0.0:8765...");
console.log("[FoxgloveClient] Attempting to connect to ws://localhost:8765...");

const client = new FoxgloveClient({
ws: new WebSocket("ws://0.0.0.0:8765", [FoxgloveClient.SUPPORTED_SUBPROTOCOL]),
ws: new WebSocket(process.env.ROSBRIDGE_URL || "ws://localhost:8765", [FoxgloveClient.SUPPORTED_SUBPROTOCOL]),
});

const deserializers = new Map();
Expand Down Expand Up @@ -75,7 +75,7 @@ async function startFoxgloveClient(sessionId) {

// Queue graph-related processing instead of direct function calls
if (["RUNNER_DEFINE", "RUNNER_EVENT"].includes(event.type)) {
graphQueueManager.addToQueue(event, sessionId);
graphQueueManager.process(event, sessionId);
}
})
.catch((err) =>
Expand All @@ -84,7 +84,7 @@ async function startFoxgloveClient(sessionId) {
});

client.on("error", (err) => {
console.error(`[FoxgloveClient] ❌ WebSocket error: ${err.message}`);
console.error(`[FoxgloveClient] ❌ WebSocket error for session ${sessionId}: ${err.message}`);
});

client.on("close", () => {
Expand Down
10 changes: 6 additions & 4 deletions backend/models/Graph.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
const mongoose = require("mongoose");

const nodeSchema = new mongoose.Schema({
nodeId: { type: Number, required: true, unique: true },
nodeId: { type: Number, required: true},
capability: String,
provider: String,
}, { _id: false });

const edgeSchema = new mongoose.Schema({
edgeId: { type: Number, required: true, unique: true },
edgeId: { type: Number, required: true},
sourceNodeID: { type: Number, required: true }, // Node ID of the source node
targetNodeID: { type: Number, required: true }, // Node ID of the target node
}, { _id: false });

const eventLogSchema = new mongoose.Schema({
eventId: { type: Number, required: true, unique: true },
eventId: { type: Number, required: true},
nodeId: { type: Number, default: null }, // Optional, can be null if not related to a node
edgeId: { type: Number, default: null }, // Optional, can be null if not related to an edge
nodeState: {
Expand All @@ -27,12 +27,14 @@ const eventLogSchema = new mongoose.Schema({
const graphSchema = new mongoose.Schema({
graphId: { type: String, required: true, unique: true },
session: { type: mongoose.Schema.Types.ObjectId, ref: "Session", required: true },
graphNo: { type: Number, required: true, unique: true },
graphNo: { type: Number, required: true },
nodes: [nodeSchema],
edges: [edgeSchema],
eventLog: [eventLogSchema],
completed: { type: Boolean, default: false },
completedAt: Date,
}, { timestamps: true });

graphSchema.index({ session: 1, graphNo: 1 }, { unique: true });

module.exports = mongoose.model("Graph", graphSchema);
28 changes: 11 additions & 17 deletions backend/routes/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,19 @@ const express = require('express');
const router = express.Router();
const Event = require('../models/Event');

// Save new event
router.post('/', async (req, res) => {
try {
const newEvent = new Event(req.body);
const saved = await newEvent.save();
res.status(201).json(saved);
} catch (err) {
res.status(400).json({ error: err.message });
}
});

// Get all events
// Get filtered events
router.get('/', async (req, res) => {
try {
const events = await Event.find().sort({ createdAt: -1 });
res.json(events);
} catch (err) {
res.status(500).json({ error: err.message });
try {
const filter = {};

if (req.query.session) {
filter.session = req.query.session;
}

const events = await Event.find(filter).sort({ createdAt: -1 });
res.json(events);
} catch (err) {
res.status(500).json({ error: err.message }); }
});

module.exports = router;
24 changes: 13 additions & 11 deletions backend/routes/graphs.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ router.post("/:sessionId/create", async (req, res) => {
const { sessionId } = req.params;
const graph = await createGraphForSession(sessionId);
if (!graph) return res.status(404).json({ message: "No RUNNER_DEFINE events found" });
console.info("Graph creation sucess",sessionId);
res.json(graph);
} catch (err) {
console.error("Graph creation failed", err.message);
Expand All @@ -17,7 +18,9 @@ router.post("/:sessionId/create", async (req, res) => {

router.get("/:sessionId", async (req, res) => {
try {
const graphs = await Graph.find({ session: req.params.sessionId }).sort({ graphNumber: 1 });
const { sessionId } = req.params;
const graphs = await Graph.find({ session: sessionId }).sort({ graphNo: 1 });
console.info("All graph count ",graphs.length);
res.json(graphs);
} catch (err) {
res.status(500).json({ error: err.message });
Expand All @@ -27,30 +30,29 @@ router.get("/:sessionId", async (req, res) => {

router.get("/:sessionId/count", async (req, res) => {
try {
const count = await Graph.countDocuments({ session: req.params.sessionId });
const { sessionId } = req.params;
const count = await Graph.countDocuments({ session: sessionId });
console.info("Graph count for session", sessionId, "is", count);
res.json({ count });
} catch (err) {
res.status(500).json({ error: err.message });
}
});

router.get("/:sessionId/:index", async (req, res) => {
const { sessionId, index } = req.params;
try {
const graph = await Graph.findOne({ session: sessionId })
.sort({ graphNo: 1 })
.skip(Number(index))
.limit(1);
const { sessionId, index } = req.params;

if (!graph) return res.status(404).json({ message: "Graph not found" });
const graph = await Graph.findOne({ session: sessionId, graphNo: parseInt(index) + 1 });

if (!graph) return res.status(404).json({ error: "Graph not found" });

res.json(graph);
} catch (err) {
res.status(500).json({ error: err.message });
console.error("Error fetching graph:", err);
res.status(500).json({ error: "Server error" });
}
});




module.exports = router;
4 changes: 2 additions & 2 deletions backend/routes/sessions.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ router.post("/", async (req, res) => {
console.log("[Session API] Created new session:", saved);

if (req.query.listen === "true") {
console.log("[Session API] Triggering FoxgloveClient for session:", saved._id);
startFoxgloveClient(saved._id);
console.log("[Session API] Triggering FoxgloveClient for session with session id:", saved._id);
startFoxgloveClient(saved._id); // ✅ correct usage
}

res.status(201).json(saved);
Expand Down
2 changes: 1 addition & 1 deletion backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ app.use("/api/sessions", sessionRoutes);
app.use("/api/events", eventRoutes);
app.use("/api/graphs", graphRoutes);

mongoose.connect(process.env.MONGO_URI || "mongodb://localhost:27017/capabilities2", {
mongoose.connect(process.env.MONGO_URI || "mongodb://localhost:27017/event_logger", {
useNewUrlParser: true,
useUnifiedTopology: true,
});
Expand Down
Loading