Skip to content

Commit 5082c7a

Browse files
committed
merge prod (with everything) into main
2 parents f98a667 + 2ae75ce commit 5082c7a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+4791
-2940
lines changed

.github/workflows/build-push-docker-images.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ on:
66

77
env:
88
REGISTRY_URL: "${{ secrets.CONTAINER_REGISTRY_URL }}"
9+
ENV_PRODUCTION_FILE: "${{ secrets.ENV_PRODUCTION_FILE }}"
910

1011
jobs:
1112
build:
1213
runs-on: ubuntu-24.04-arm
1314
steps:
1415
- uses: actions/checkout@v4
15-
- name: Login to GitHub Package Registry
16-
run: echo "${{ secrets.CONTAINER_REGISTRY_PASSWORD }}" | docker login $REGISTRY_URL -u "${{ secrets.CONTAINER_REGISTRY_USERNAME }}" --password-stdin
16+
- run: echo "${{ secrets.CONTAINER_REGISTRY_PASSWORD }}" | docker login $REGISTRY_URL -u "${{ secrets.CONTAINER_REGISTRY_USERNAME }}" --password-stdin
17+
- run: echo "$ENV_PRODUCTION_FILE" > frontend/.env.production
1718
- run: docker compose build
1819
- run: docker compose push
20+
- run: docker logout $REGISTRY_URL

backend/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.12-slim
1+
FROM python:3.12
22

33
WORKDIR /app
44

backend/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ pyarrow-stubs
55
websockets==14.0
66
aiohttp==3.11.10
77
aiohttp_cors==0.7
8+
cantools>=40.2.1

backend/src/http_server.py

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
import os
22
from aiohttp import web
33
import aiohttp_cors
4+
import cantools
45

5-
dirs = list(filter(None, os.environ.get("RECORDING_DIRS", "").split(":")))
6+
data_dir = os.environ.get("DATA_DIR", "")
67
parquet_files = set()
7-
for toplevel_dir in dirs:
8-
if not os.path.isdir(toplevel_dir):
9-
print("Invalid recording dir: ", toplevel_dir)
10-
continue
8+
if not os.path.isdir(data_dir):
9+
raise ValueError("Invalid recording dir: ", data_dir)
1110

12-
def check_subdir(d):
13-
for f in os.listdir(d):
14-
path = os.path.join(d, f)
15-
if os.path.isdir(path):
16-
check_subdir(path)
17-
elif f.endswith(".pq") or f.endswith(".parquet"):
18-
parquet_files.add(os.path.relpath(path, toplevel_dir))
1911

20-
check_subdir(toplevel_dir)
12+
def check_subdir(d):
13+
for f in os.listdir(d):
14+
path = os.path.join(d, f)
15+
if os.path.isdir(path):
16+
check_subdir(path)
17+
elif f.endswith(".pq") or f.endswith(".parquet"):
18+
parquet_files.add(os.path.relpath(path, data_dir))
19+
20+
21+
check_subdir(data_dir)
2122

2223

2324
routes = web.RouteTableDef()
@@ -27,16 +28,17 @@ def check_subdir(d):
2728
async def available_recordings(request):
2829
return web.json_response(list(parquet_files))
2930

30-
if len(dirs) > 0:
31-
# TODO: decide on multuple dirs
32-
routes.static("/api/get-recording", dirs[0], show_index=True)
33-
# @routes.get("/api/get-recording/{filename}")
34-
# async def get_recording(request: web.Request):
35-
# f = request.match_info.get("filename")
36-
# if not f:
37-
# return web.Response(status=404, body="Unkown / incorrect recording filename!")
38-
# # return web.Response(content_type="application/vnd.apache.arrow.stream", body=)
39-
# return web.json_response({"you asked for": f})
31+
32+
# This serves the parquet data directly as static files
33+
routes.static("/api/get-recording", data_dir, show_index=True)
34+
35+
36+
@routes.get("/api/get-dbc/{car}")
37+
async def get_dbc(request: web.Request):
38+
car = request.match_info.get("car")
39+
db = cantools.db.database.Database()
40+
41+
return web.json_response(response_obj)
4042

4143

4244
app = web.Application()

backend/src/util/dbc_to_json.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import cantools
2+
import json
3+
4+
def message_to_obj(msg: cantools.db.Message):
5+
return {
6+
"frame_id": msg.frame_id,
7+
"name": msg.name,
8+
"length": msg.length,
9+
"signals": msg.signals,
10+
# if the message is a container message, this lists
11+
# the messages which it potentially features
12+
"contained_messages": [message_to_obj(m) for m in msg.contained_messages or []],
13+
# header ID of message if it is part of a container message
14+
"header_id": msg.header_id,
15+
"header_byte_order": msg.header_byte_order,
16+
"unused_bit_pattern": msg.unused_bit_pattern,
17+
"comment": msg.comment,
18+
"senders": msg.senders,
19+
"send_type": msg.send_type,
20+
"cycle_time": msg.cycle_time,
21+
"is_extended_frame": msg.is_extended_frame,
22+
"is_fd": msg.is_fd,
23+
"bus_name": msg.bus_name,
24+
"signal_groups": msg.signal_groups,
25+
"protocol": msg.protocol,
26+
}
27+
28+
def signal_to_obj(sig: cantools.db.Signal):
29+
return {
30+
"name": sig.name,
31+
"start": sig.start,
32+
"length": sig.length,
33+
"byte_order": sig.byte_order,
34+
"is_signed": sig.is_signed,
35+
"raw_initial": sig.raw_initial,
36+
"raw_invalid": sig.raw_invalid,
37+
"conversion": sig.conversion,
38+
"minimum": sig.maximum,
39+
"maximum": sig.minimum,
40+
"unit": sig.unit,
41+
"comment": sig.comment,
42+
"receivers": sig.receivers,
43+
"is_multiplexer": sig.is_multiplexer,
44+
"multiplexer_ids": sig.multiplexer_ids,
45+
"multiplexer_signal": sig.multiplexer_signal,
46+
"spn": sig.spn,
47+
}
48+
49+
def dbc_to_json(db: cantools.db.Database):
50+
response_obj = {
51+
"version": db.version,
52+
"messages": [message_to_obj(msg) for msg in db.messages],
53+
"nodes": db.nodes,
54+
"buses": db.buses,
55+
}
File renamed without changes.

backend/src/websocket_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pyarrow as pa
77
import random
88

9-
import sim
9+
import util.sim as sim
1010

1111
# List of WebSocket connections with their corresponding Arrow Stream (IPC)
1212
# Writers and BytesIO buffers

compose.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ services:
1717
# Nodejs process hosts react frontend
1818
nodejs:
1919
image: "${REGISTRY_URL:-}telemetry-vis-software/frontend:latest"
20+
# NOTE: in order for the build to produce a working output, .env.production
21+
# needs to be present in the build context dir with lcjs license information.
2022
build: ./frontend
2123
expose:
2224
- "3000"
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// import { RangeSlider, RangeSliderProps, RangeSliderValue } from "@mantine/core";
2+
// import { useEffect, useRef, useState } from "react";
3+
//
4+
// interface DraggableRangeSliderProps extends RangeSliderProps {
5+
// draggableBarHeightPx?: number;
6+
// value: RangeSliderValue;
7+
// onChange: (value: RangeSliderValue) => void;
8+
// }
9+
//
10+
// export default function DraggableRangeSlider({
11+
// draggableBarHeightPx,
12+
// value,
13+
// onChange,
14+
// ...props
15+
// }: DraggableRangeSliderProps) {
16+
// const [range, setRange] = [value, onChange];
17+
//
18+
// const draggingRef = useRef(false);
19+
// const lastMouseX = useRef(0);
20+
// const sliderRef = useRef<HTMLDivElement>(null);
21+
// const width = range[1] - range[0];
22+
//
23+
// const handleMouseDown = (event: MouseEvent) => {
24+
// draggingRef.current = true;
25+
// lastMouseX.current = event.clientX;
26+
// };
27+
//
28+
// const handleMouseUp = () => {
29+
// draggingRef.current = false;
30+
// };
31+
//
32+
// const handleMouseMove = (event: MouseEvent) => {
33+
// if (!draggingRef.current || !sliderRef.current) return;
34+
//
35+
// const deltaX = event.clientX - lastMouseX.current;
36+
// lastMouseX.current = event.clientX;
37+
//
38+
// // Convert pixels to range values proportionally
39+
// const sliderWidth = sliderRef.current.clientWidth;
40+
// const rangeWidth = 100; // Assuming the slider goes from 0 to 100
41+
// const scaleFactor = rangeWidth / sliderWidth; // Converts pixels to value change
42+
//
43+
// const deltaValue = deltaX * scaleFactor; // Smooth movement
44+
//
45+
// setRange(([start, end]) => {
46+
// let newStart = start + deltaValue;
47+
// let newEnd = end + deltaValue;
48+
//
49+
// // Prevent exceeding min/max limits
50+
// if (newStart < 0) {
51+
// newStart = 0;
52+
// newEnd = end - start; // Maintain width
53+
// }
54+
// if (newEnd > 100) {
55+
// newEnd = 100;
56+
// newStart = newEnd - (end - start); // Maintain width
57+
// }
58+
//
59+
// return [newStart, newEnd];
60+
// });
61+
// };
62+
//
63+
// useEffect(() => {
64+
// document.addEventListener("mousemove", handleMouseMove);
65+
// document.addEventListener("mouseup", handleMouseUp);
66+
// return () => {
67+
// document.removeEventListener("mousemove", handleMouseMove);
68+
// document.removeEventListener("mouseup", handleMouseUp);
69+
// };
70+
// }, []);
71+
//
72+
// return (
73+
// <div ref={sliderRef} className="relative w-full">
74+
// <RangeSlider
75+
// value={range}
76+
// onChange={setRange}
77+
// styles={{
78+
// track: { cursor: "pointer" },
79+
// bar: { cursor: "pointer" },
80+
// }}
81+
// {...props}
82+
// />
83+
// {/* Invisible overlay to detect middle drag */}
84+
// <div
85+
// className="absolute top-0 left-0 h-full bg-red-300"
86+
// style={{
87+
// cursor: "grab",
88+
// width: `${width - width * 0.1}%`,
89+
// left: `${range[0] + width * 0.05}%`,
90+
// height: "20px", // Make it easy to grab
91+
// backgroundColor: "transparent",
92+
// zIndex: 10,
93+
// top: -(20 - (sliderRef.current ? sliderRef.current.offsetHeight : 0)) / 2,
94+
// }}
95+
// onMouseDown={handleMouseDown}
96+
// />
97+
// </div>
98+
// );
99+
// }

0 commit comments

Comments
 (0)