From c4caf31695d247da5f786709fd3b01d545e953db Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:56:43 +0100 Subject: [PATCH 01/18] feat: add .gitignore for packet-sender --- packet-sender/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 packet-sender/.gitignore diff --git a/packet-sender/.gitignore b/packet-sender/.gitignore new file mode 100644 index 000000000..6d23150b2 --- /dev/null +++ b/packet-sender/.gitignore @@ -0,0 +1,2 @@ +# Rust target directory +/target/ \ No newline at end of file From 708f32713813ce90732aa0d7c50ed35fca671402 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:58:38 +0100 Subject: [PATCH 02/18] feat: add no-sandbox fix for linux --- electron-app/main.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/electron-app/main.js b/electron-app/main.js index ef5841b58..fd7a0a40d 100644 --- a/electron-app/main.js +++ b/electron-app/main.js @@ -15,6 +15,22 @@ import { createWindow } from "./src/windows/mainWindow.js"; const { autoUpdater } = pkg; +// Disable sandbox for Linux +if (process.platform === "linux") { + try { + const userns = fs + .readFileSync("/proc/sys/kernel/unprivileged_userns_clone", "utf8") + .trim(); + if (userns === "0") { + app.commandLine.appendSwitch("no-sandbox"); + } + } catch (e) {} + + if (process.getuid && process.getuid() === 0) { + app.commandLine.appendSwitch("no-sandbox"); + } +} + // Setup IPC handlers for renderer process communication setupIpcHandlers(); From a50bdc2059ccb7e4399260bb9cfd29721e286afe Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Fri, 20 Feb 2026 08:46:54 +0100 Subject: [PATCH 03/18] feat: some packet-sender tweaks --- backend/cmd/main.go | 2 +- backend/cmd/orchestrator.go | 2 +- backend/cmd/setup_transport.go | 2 +- backend/cmd/setup_vehicle.go | 2 +- backend/internal/pod_data/measurement.go | 2 +- backend/internal/pod_data/pod_data.go | 2 +- backend/{internal => pkg}/adj/adj.go | 0 backend/{internal => pkg}/adj/boards.go | 0 backend/{internal => pkg}/adj/git.go | 0 backend/{internal => pkg}/adj/models.go | 0 electron-app/build.mjs | 106 +++++++++------------ electron-app/src/menu/menu.js | 2 +- electron-app/src/processes/packetSender.js | 16 +++- electron-app/src/utils/paths.js | 7 -- go.work | 1 + packet-sender/.gitignore | 2 - packet-sender/main.go | 14 +-- packet-sender/package.json | 13 +++ 18 files changed, 87 insertions(+), 86 deletions(-) rename backend/{internal => pkg}/adj/adj.go (100%) rename backend/{internal => pkg}/adj/boards.go (100%) rename backend/{internal => pkg}/adj/git.go (100%) rename backend/{internal => pkg}/adj/models.go (100%) delete mode 100644 packet-sender/.gitignore create mode 100644 packet-sender/package.json diff --git a/backend/cmd/main.go b/backend/cmd/main.go index addc5c879..b31a17d63 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -5,12 +5,12 @@ import ( "os" "os/signal" - adj_module "github.com/HyperloopUPV-H8/h9-backend/internal/adj" "github.com/HyperloopUPV-H8/h9-backend/internal/config" "github.com/HyperloopUPV-H8/h9-backend/internal/flags" "github.com/HyperloopUPV-H8/h9-backend/internal/pod_data" "github.com/HyperloopUPV-H8/h9-backend/internal/update_factory" vehicle_models "github.com/HyperloopUPV-H8/h9-backend/internal/vehicle/models" + adj_module "github.com/HyperloopUPV-H8/h9-backend/pkg/adj" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport" "github.com/HyperloopUPV-H8/h9-backend/pkg/websocket" trace "github.com/rs/zerolog/log" diff --git a/backend/cmd/orchestrator.go b/backend/cmd/orchestrator.go index 180b26c8d..6d34b064a 100644 --- a/backend/cmd/orchestrator.go +++ b/backend/cmd/orchestrator.go @@ -7,11 +7,11 @@ import ( "runtime/pprof" "strings" - adj_module "github.com/HyperloopUPV-H8/h9-backend/internal/adj" "github.com/HyperloopUPV-H8/h9-backend/internal/config" "github.com/HyperloopUPV-H8/h9-backend/internal/flags" "github.com/HyperloopUPV-H8/h9-backend/internal/pod_data" "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" + adj_module "github.com/HyperloopUPV-H8/h9-backend/pkg/adj" "github.com/HyperloopUPV-H8/h9-backend/pkg/logger" data_logger "github.com/HyperloopUPV-H8/h9-backend/pkg/logger/data" order_logger "github.com/HyperloopUPV-H8/h9-backend/pkg/logger/order" diff --git a/backend/cmd/setup_transport.go b/backend/cmd/setup_transport.go index c86f9270e..9c9a5b628 100644 --- a/backend/cmd/setup_transport.go +++ b/backend/cmd/setup_transport.go @@ -7,12 +7,12 @@ import ( "net" "time" - adj_module "github.com/HyperloopUPV-H8/h9-backend/internal/adj" "github.com/HyperloopUPV-H8/h9-backend/internal/common" "github.com/HyperloopUPV-H8/h9-backend/internal/config" "github.com/HyperloopUPV-H8/h9-backend/internal/pod_data" "github.com/HyperloopUPV-H8/h9-backend/internal/utils" "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" + adj_module "github.com/HyperloopUPV-H8/h9-backend/pkg/adj" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/network/tcp" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/network/udp" diff --git a/backend/cmd/setup_vehicle.go b/backend/cmd/setup_vehicle.go index b8a7b62e0..8fe8999e2 100644 --- a/backend/cmd/setup_vehicle.go +++ b/backend/cmd/setup_vehicle.go @@ -12,12 +12,12 @@ import ( h "github.com/HyperloopUPV-H8/h9-backend/pkg/http" "github.com/HyperloopUPV-H8/h9-backend/pkg/websocket" - adj_module "github.com/HyperloopUPV-H8/h9-backend/internal/adj" "github.com/HyperloopUPV-H8/h9-backend/internal/common" "github.com/HyperloopUPV-H8/h9-backend/internal/config" "github.com/HyperloopUPV-H8/h9-backend/internal/pod_data" "github.com/HyperloopUPV-H8/h9-backend/internal/update_factory" "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" + adj_module "github.com/HyperloopUPV-H8/h9-backend/pkg/adj" "github.com/HyperloopUPV-H8/h9-backend/pkg/broker" connection_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/connection" data_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/data" diff --git a/backend/internal/pod_data/measurement.go b/backend/internal/pod_data/measurement.go index 446b88611..b2581c4e3 100644 --- a/backend/internal/pod_data/measurement.go +++ b/backend/internal/pod_data/measurement.go @@ -4,9 +4,9 @@ import ( "fmt" "strings" - "github.com/HyperloopUPV-H8/h9-backend/internal/adj" "github.com/HyperloopUPV-H8/h9-backend/internal/common" "github.com/HyperloopUPV-H8/h9-backend/internal/utils" + "github.com/HyperloopUPV-H8/h9-backend/pkg/adj" ) const EnumType = "enum" diff --git a/backend/internal/pod_data/pod_data.go b/backend/internal/pod_data/pod_data.go index 7cafa1411..3f091aaaa 100644 --- a/backend/internal/pod_data/pod_data.go +++ b/backend/internal/pod_data/pod_data.go @@ -3,8 +3,8 @@ package pod_data import ( "github.com/HyperloopUPV-H8/h9-backend/internal/utils" - "github.com/HyperloopUPV-H8/h9-backend/internal/adj" "github.com/HyperloopUPV-H8/h9-backend/internal/common" + "github.com/HyperloopUPV-H8/h9-backend/pkg/adj" ) func NewPodData(adjBoards map[string]adj.Board, globalUnits map[string]utils.Operations) (PodData, error) { diff --git a/backend/internal/adj/adj.go b/backend/pkg/adj/adj.go similarity index 100% rename from backend/internal/adj/adj.go rename to backend/pkg/adj/adj.go diff --git a/backend/internal/adj/boards.go b/backend/pkg/adj/boards.go similarity index 100% rename from backend/internal/adj/boards.go rename to backend/pkg/adj/boards.go diff --git a/backend/internal/adj/git.go b/backend/pkg/adj/git.go similarity index 100% rename from backend/internal/adj/git.go rename to backend/pkg/adj/git.go diff --git a/backend/internal/adj/models.go b/backend/pkg/adj/models.go similarity index 100% rename from backend/internal/adj/models.go rename to backend/pkg/adj/models.go diff --git a/electron-app/build.mjs b/electron-app/build.mjs index f83964a19..b4dff1753 100644 --- a/electron-app/build.mjs +++ b/electron-app/build.mjs @@ -5,7 +5,7 @@ */ import { execSync } from "child_process"; -import { copyFileSync, cpSync, existsSync, mkdirSync, rmSync } from "fs"; +import { cpSync, existsSync, mkdirSync, rmSync } from "fs"; import { dirname, join } from "path"; import { fileURLToPath } from "url"; import { logger } from "./src/utils/logger.js"; @@ -20,6 +20,7 @@ const CONFIG = { type: "go", path: join(ROOT, "backend"), // Root of backend (where package.json is) output: join(__dirname, "binaries"), + entry: "./cmd", commands: ["pnpm run build:ci"], platforms: [ { @@ -52,18 +53,43 @@ const CONFIG = { }, ], }, - "packet-sender": { - type: "rust", - path: join(ROOT, "packet-sender"), - output: join(__dirname, "binaries"), - commands: ["pnpm run build"], - binaryPath: "target/release/packet-sender", - platforms: [ - { id: "win64", ext: ".exe", tags: ["win", "windows"] }, - { id: "linux64", ext: "", tags: ["linux"] }, - { id: "mac64", ext: "", tags: ["mac", "macos"] }, - ], - }, + // "packet-sender": { + // type: "go", + // path: join(ROOT, "packet-sender"), + // output: join(__dirname, "binaries"), + // entry: ".", + // commands: ["pnpm run build:ci"], + // platforms: [ + // { + // id: "win64", + // goos: "windows", + // goarch: "amd64", + // ext: ".exe", + // tags: ["win", "windows"], + // }, + // { + // id: "linux64", + // goos: "linux", + // goarch: "amd64", + // ext: "", + // tags: ["linux"], + // }, + // { + // id: "mac64", + // goos: "darwin", + // goarch: "amd64", + // ext: "", + // tags: ["mac", "macos"], + // }, + // { + // id: "macArm", + // goos: "darwin", + // goarch: "arm64", + // ext: "", + // tags: ["mac", "macos"], + // }, + // ], + // }, "testing-view": { type: "frontend", path: join(ROOT, "frontend/testing-view"), @@ -98,8 +124,8 @@ const run = (cmd, cwd, env = {}) => { } }; -const buildBackend = (config, requestedPlatforms, extraArgs = "") => { - logger.info("Building Backend (Go)..."); +const buildGo = (name, config, requestedPlatforms, extraArgs = "") => { + logger.info(`Building ${name} (Go)...`); mkdirSync(config.output, { recursive: true }); const targets = config.platforms.filter((p) => { @@ -112,22 +138,15 @@ const buildBackend = (config, requestedPlatforms, extraArgs = "") => { return p.tags.some((tag) => requestedPlatforms.includes(tag)); }); - if (targets.length === 0) { - logger.error( - `No matching platforms found for: ${requestedPlatforms.join(", ")}` - ); - return false; - } - let success = true; for (const p of targets) { - const filename = `backend-${p.goos}-${p.goarch}${p.ext}`; + const filename = `${name}-${p.goos}-${p.goarch}${p.ext}`; logger.step(`Building ${p.goos}/${p.goarch}...`); + const entryPath = config.entry || "."; + for (const cmd of config.commands) { - // cmd is like "pnpm run build:ci --" - // We append the output flag and target directory - const buildCmd = `${cmd} -o "${join(config.output, filename)}" ${extraArgs} ./cmd`; + const buildCmd = `${cmd} -o "${join(config.output, filename)}" ${extraArgs} ${entryPath}`; const result = run(buildCmd, config.path, { GOOS: p.goos, @@ -145,37 +164,6 @@ const buildBackend = (config, requestedPlatforms, extraArgs = "") => { return success; }; -const buildRust = (name, config, requestedPlatforms, extraArgs = "") => { - logger.info(`Building ${name} (Rust)...`); - mkdirSync(config.output, { recursive: true }); - - for (const cmd of config.commands) { - // Only append extra args to build commands - const finalCmd = cmd.includes("build") ? `${cmd} ${extraArgs}` : cmd; - if (!run(finalCmd, config.path)) return false; - } - - const isWin = - process.platform === "win32" || - (requestedPlatforms && requestedPlatforms.includes("win")); - const ext = isWin ? ".exe" : ""; - - // Check for source binary - const sourceBin = join(config.path, config.binaryPath + ext); - const destName = `packet-sender${ext}`; - const destPath = join(config.output, destName); - - logger.step(`Copying binary to ${destPath}...`); - - if (existsSync(sourceBin)) { - copyFileSync(sourceBin, destPath); - return true; - } else { - logger.error(`Rust binary not found at ${sourceBin}`); - return false; - } -}; - const buildFrontend = (name, config, extraArgs = "") => { if (config.optional && !existsSync(join(config.path, "package.json"))) { logger.warning(`Skipping ${name} (not initialized)`); @@ -252,9 +240,7 @@ logger.header("Hyperloop Control Station Build"); let success = true; if (config.type === "go") { - success = buildBackend(config, requestedPlatforms, extraArgs); - } else if (config.type === "rust") { - success = buildRust(key, config, requestedPlatforms, extraArgs); + success = buildGo(key, config, requestedPlatforms, extraArgs); } else if (config.type === "frontend") { success = buildFrontend(key, config, extraArgs); if (success && !config.optional) frontendBuilt = true; diff --git a/electron-app/src/menu/menu.js b/electron-app/src/menu/menu.js index c0436ab79..64f776da9 100644 --- a/electron-app/src/menu/menu.js +++ b/electron-app/src/menu/menu.js @@ -83,7 +83,7 @@ function createMenu(mainWindow) { } const packetSenderProcess = getPacketSenderProcess(); if (!packetSenderProcess || packetSenderProcess.killed) { - startPacketSender(["random"]); + startPacketSender(); } }, }, diff --git a/electron-app/src/processes/packetSender.js b/electron-app/src/processes/packetSender.js index e6efc5cf9..74b653fa1 100644 --- a/electron-app/src/processes/packetSender.js +++ b/electron-app/src/processes/packetSender.js @@ -12,16 +12,18 @@ import { getBinaryPath } from "../utils/paths.js"; // Store the packet sender process instance let packetSenderProcess = null; +// Default arguments for packet sender +const DEFAULT_ARGS = ["1", "1"]; // Send mode, Random type + /** * Starts the packet sender process by spawning the packet-sender binary with optional arguments. * Sets up event handlers for stdout and stderr with appropriate logging. * @param {string[]} [args=[]] - Optional array of command-line arguments to pass to the packet-sender binary. * @returns {import("child_process").ChildProcessWithoutNullStreams | null} The spawned ChildProcess object, or null if the binary is not found. * @example - * const process = startPacketSender(["--port", "8080"]); * startPacketSender(); */ -function startPacketSender(args = []) { +function startPacketSender(args = DEFAULT_ARGS) { // Get the path to the packet-sender binary const packetSenderBin = getBinaryPath("packet-sender"); @@ -44,6 +46,14 @@ function startPacketSender(args = []) { // Log stdout output from packet sender process.stdout.on("data", (data) => { logger.packetSender.info(`${data.toString().trim()}`); + + if (data.toString().includes("1) Send packets")) { + process.stdin.write("1\n"); + } + + if (data.toString().includes("1) Random")) { + process.stdin.write("1\n"); + } }); // Log stderr output as errors @@ -90,7 +100,7 @@ function restartPacketSender() { // Wait before starting new process to ensure cleanup setTimeout(() => { // Start with help arguments - startPacketSender(["random"]); + startPacketSender(); }, 500); } } diff --git a/electron-app/src/utils/paths.js b/electron-app/src/utils/paths.js index f7bf70033..1d44ceb39 100644 --- a/electron-app/src/utils/paths.js +++ b/electron-app/src/utils/paths.js @@ -41,13 +41,6 @@ function getBinaryPath(name) { const arch = process.arch; const ext = platform === "win32" ? ".exe" : ""; - if (name === "packet-sender") { - if (!app.isPackaged) { - return path.join(getAppPath(), "binaries", `${name}${ext}`); - } - return path.join(process.resourcesPath, "binaries", `${name}${ext}`); - } - const goosMap = { win32: "windows", darwin: "darwin", diff --git a/go.work b/go.work index 521811c76..957518330 100644 --- a/go.work +++ b/go.work @@ -2,4 +2,5 @@ go 1.23.1 use ( ./backend + ./packet-sender ) diff --git a/packet-sender/.gitignore b/packet-sender/.gitignore deleted file mode 100644 index 6d23150b2..000000000 --- a/packet-sender/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Rust target directory -/target/ \ No newline at end of file diff --git a/packet-sender/main.go b/packet-sender/main.go index 9a52ccb99..f748519f3 100644 --- a/packet-sender/main.go +++ b/packet-sender/main.go @@ -7,8 +7,6 @@ import ( boardpkg "packet_sender/pkg/board" "packet_sender/pkg/listener" "packet_sender/pkg/sender" - "path" - "path/filepath" "strings" adj_module "github.com/HyperloopUPV-H8/h9-backend/pkg/adj" @@ -66,11 +64,13 @@ func getConn(lip string, lport uint16, rip string, rport uint16) *net.UDPConn { // getADJ loads the same ADJ used by backend directly from backend/cmd/adj. func getADJ() adj_module.ADJ { - adjPath, err := filepath.Abs(path.Join("..", "backend", "cmd", "adj")) - if err != nil { - log.Fatalf("Failed to resolve ADJ path: %v", err) - } - adj_module.RepoPath = adjPath + string(filepath.Separator) + // adjPath, err := filepath.Abs(path.Join("..", "backend", "cmd", "adj")) + // if err != nil { + // log.Fatalf("Failed to resolve ADJ path: %v", err) + // } + // adj_module.RepoPath = adjPath + string(filepath.Separator) + + // Uses the same ADJ RepoPath as the backend by default adj, err := adj_module.NewADJ("") if err != nil { diff --git a/packet-sender/package.json b/packet-sender/package.json new file mode 100644 index 000000000..4842b09d2 --- /dev/null +++ b/packet-sender/package.json @@ -0,0 +1,13 @@ +{ + "name": "packet-sender", + "version": "1.0.0", + "private": true, + "author": "Hyperloop UPV Team", + "license": "MIT", + "scripts": { + "build": "go build -o packet-sender main.go", + "build:ci": "go build", + "dev": "go run main.go", + "test": "go test ./..." + } +} From 715edba7a9f549d49517e8183602479202a8e108 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:18:14 +0100 Subject: [PATCH 04/18] feat: add global boards state --- .../settings/MultiCheckboxField.tsx | 36 +++++++++++-------- .../src/components/settings/SettingsForm.tsx | 11 ++++-- frontend/testing-view/src/constants/boards.ts | 10 ------ .../src/constants/settingsSchema.ts | 6 ++-- .../components/FilterCategoryItem.tsx | 4 +-- .../filtering/components/FilterController.tsx | 5 +-- .../filtering/store/filteringSlice.ts | 15 ++++---- .../rightSidebar/sections/CommandsSection.tsx | 26 ++++++++------ .../sections/TelemetrySection.tsx | 28 ++++++++------- .../testing-view/src/hooks/useBoardData.ts | 2 ++ .../src/hooks/useTransformedBoards.ts | 10 +++++- frontend/testing-view/src/lib/utils.ts | 8 ++--- .../src/store/slices/catalogSlice.ts | 6 ++++ packet-sender/package.json | 1 - 14 files changed, 97 insertions(+), 71 deletions(-) delete mode 100644 frontend/testing-view/src/constants/boards.ts diff --git a/frontend/testing-view/src/components/settings/MultiCheckboxField.tsx b/frontend/testing-view/src/components/settings/MultiCheckboxField.tsx index eb3980a2f..8ece18567 100644 --- a/frontend/testing-view/src/components/settings/MultiCheckboxField.tsx +++ b/frontend/testing-view/src/components/settings/MultiCheckboxField.tsx @@ -20,21 +20,27 @@ export const MultiCheckboxField = ({
- {field.options?.map((opt) => ( -
- handleToggle(opt, !!checked)} - /> - -
- ))} + {!field.options || field.options.length === 0 ? ( +

+ No boards detected. Connect to the backend to see available options. +

+ ) : ( + field.options?.map((opt) => ( +
+ handleToggle(opt, !!checked)} + /> + +
+ )) + )}
); diff --git a/frontend/testing-view/src/components/settings/SettingsForm.tsx b/frontend/testing-view/src/components/settings/SettingsForm.tsx index 96357b57e..c69512c55 100644 --- a/frontend/testing-view/src/components/settings/SettingsForm.tsx +++ b/frontend/testing-view/src/components/settings/SettingsForm.tsx @@ -1,5 +1,7 @@ import { get, set } from "lodash"; -import { SETTINGS_SCHEMA } from "../../constants/settingsSchema"; +import { useMemo } from "react"; +import { getSettingsSchema } from "../../constants/settingsSchema"; +import { useStore } from "../../store/store"; import type { ConfigData } from "../../types/common/config"; import type { SettingField } from "../../types/common/settings"; import { BooleanField } from "./BooleanField"; @@ -23,6 +25,9 @@ export const SettingsForm = ({ config, onChange }: SettingsFormProps) => { onChange(nextConfig); }; + const boards = useStore((s) => s.boards); + const schema = useMemo(() => getSettingsSchema(boards), [boards]); + const renderField = (field: SettingField) => { const currentValue = get(config, field.path); @@ -94,9 +99,9 @@ export const SettingsForm = ({ config, onChange }: SettingsFormProps) => { return (
- {SETTINGS_SCHEMA.map((section) => ( + {schema.map((section) => (
-

+

{section.title}

diff --git a/frontend/testing-view/src/constants/boards.ts b/frontend/testing-view/src/constants/boards.ts deleted file mode 100644 index 68a8a4127..000000000 --- a/frontend/testing-view/src/constants/boards.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** List of names of available boards. */ -export const BOARD_NAMES: readonly string[] = [ - "BCU", // Battery Control Unit - "PCU", // Propulsion Control Unit - "LCU", // Levitation Control Unit - "HVSCU", // High Voltage System Control Unit - "BMSL", // Battery Management System Level - "VCU", // Vehicle Control Unit - "HVSCU-Cabinet", // High Voltage System Control Unit Cabinet -]; diff --git a/frontend/testing-view/src/constants/settingsSchema.ts b/frontend/testing-view/src/constants/settingsSchema.ts index eecc75f85..28561ea8c 100644 --- a/frontend/testing-view/src/constants/settingsSchema.ts +++ b/frontend/testing-view/src/constants/settingsSchema.ts @@ -1,8 +1,8 @@ import type { SettingsSection } from "../types/common/settings"; -import { BOARD_NAMES } from "./boards"; +import type { BoardName } from "../types/data/board"; /** Settings form is generated from this schema. */ -export const SETTINGS_SCHEMA: SettingsSection[] = [ +export const getSettingsSchema = (boards: BoardName[]): SettingsSection[] => [ { title: "Vehicle Configuration", fields: [ @@ -10,7 +10,7 @@ export const SETTINGS_SCHEMA: SettingsSection[] = [ label: "Boards", path: "vehicle.boards", type: "multi-checkbox", - options: BOARD_NAMES as string[], + options: boards, }, ], }, diff --git a/frontend/testing-view/src/features/filtering/components/FilterCategoryItem.tsx b/frontend/testing-view/src/features/filtering/components/FilterCategoryItem.tsx index 747f80aff..05f2a2467 100644 --- a/frontend/testing-view/src/features/filtering/components/FilterCategoryItem.tsx +++ b/frontend/testing-view/src/features/filtering/components/FilterCategoryItem.tsx @@ -22,7 +22,7 @@ export const FilterCategoryItem = ({ category }: FilterCategoryItemProps) => { const toggleCategoryFilter = useStore((s) => s.toggleCategoryFilter); const toggleItemFilter = useStore((s) => s.toggleItemFilter); - const items = useStore((s) => s.getCatalog(scope)[category]); + const items = useStore((s) => s.getCatalog(scope)[category]) || []; const totalItems = items.length; const selectedIds = useStore( @@ -61,7 +61,7 @@ export const FilterCategoryItem = ({ category }: FilterCategoryItemProps) => {
- {items.map((item) => ( + {items?.map((item) => ( { const { isOpen, scope } = useStore((s) => s.filterDialog); const close = useStore((s) => s.closeFilterDialog); + const boards = useStore((s) => s.boards); + const clearFilters = useStore((s) => s.clearFilters); const selectAllFilters = useStore((s) => s.selectAllFilters); @@ -20,7 +21,7 @@ export const FilterController = () => { onClose={close} onClearAll={() => clearFilters(scope)} onSelectAll={() => selectAllFilters(scope)} - categories={BOARD_NAMES} + categories={boards} FilterCategoryComponent={FilterCategoryItem} /> ); diff --git a/frontend/testing-view/src/features/filtering/store/filteringSlice.ts b/frontend/testing-view/src/features/filtering/store/filteringSlice.ts index 1bcc4cc6d..ecb047379 100644 --- a/frontend/testing-view/src/features/filtering/store/filteringSlice.ts +++ b/frontend/testing-view/src/features/filtering/store/filteringSlice.ts @@ -135,7 +135,7 @@ export const createFilteringSlice: StateCreator< const currentWorkspaceFilters = get().workspaceFilters[workspaceId] || {}; const currentTabFilter = - currentWorkspaceFilters[scope] || createEmptyFilter(); + currentWorkspaceFilters[scope] || createEmptyFilter(get().boards); const currentCategoryIds = currentTabFilter[category] || []; @@ -157,13 +157,13 @@ export const createFilteringSlice: StateCreator< const items = get().getCatalog(scope); - const fullFilter = createFullFilter(items); + const fullFilter = createFullFilter(items, get().boards); get().updateFilters(scope, fullFilter); }, clearFilters: (scope) => { const workspaceId = get().getActiveWorkspaceId(); if (!workspaceId) return; - const emptyFilter = createEmptyFilter(); + const emptyFilter = createEmptyFilter(get().boards); get().updateFilters(scope, emptyFilter); }, toggleCategoryFilter: (scope, category, checked) => { @@ -173,7 +173,8 @@ export const createFilteringSlice: StateCreator< const catalog = get().getCatalog(scope); const currentFilters = - get().workspaceFilters[workspaceId]?.[scope] || createEmptyFilter(); + get().workspaceFilters[workspaceId]?.[scope] || + createEmptyFilter(get().boards); const newItems = checked ? catalog?.[category]?.map((item) => item.id) || [] @@ -196,9 +197,9 @@ export const createFilteringSlice: StateCreator< if (Object.keys(currentFilters).length === 0) { set({ workspaceFilters: generateInitialFilters({ - commands: createFullFilter(commands), - telemetry: createFullFilter(telemetry), - logs: createFullFilter(telemetry), + commands: createFullFilter(commands, get().boards), + telemetry: createFullFilter(telemetry, get().boards), + logs: createFullFilter(telemetry, get().boards), }), }); } diff --git a/frontend/testing-view/src/features/workspace/components/rightSidebar/sections/CommandsSection.tsx b/frontend/testing-view/src/features/workspace/components/rightSidebar/sections/CommandsSection.tsx index 60b1a0876..59b3dbaff 100644 --- a/frontend/testing-view/src/features/workspace/components/rightSidebar/sections/CommandsSection.tsx +++ b/frontend/testing-view/src/features/workspace/components/rightSidebar/sections/CommandsSection.tsx @@ -1,15 +1,19 @@ -import { BOARD_NAMES } from "../../../../../constants/boards"; +import { useStore } from "../../../../../store/store"; import type { CommandCatalogItem } from "../../../../../types/data/commandCatalogItem"; import { CommandItem } from "../tabs/commands/CommandItem"; import { Tab } from "../tabs/Tab"; -export const CommandsSection = () => ( - ( - - )} - /> -); +export const CommandsSection = () => { + const boards = useStore((s) => s.boards); + + return ( + ( + + )} + /> + ); +}; diff --git a/frontend/testing-view/src/features/workspace/components/rightSidebar/sections/TelemetrySection.tsx b/frontend/testing-view/src/features/workspace/components/rightSidebar/sections/TelemetrySection.tsx index 4798da54d..3cb19cfce 100644 --- a/frontend/testing-view/src/features/workspace/components/rightSidebar/sections/TelemetrySection.tsx +++ b/frontend/testing-view/src/features/workspace/components/rightSidebar/sections/TelemetrySection.tsx @@ -1,16 +1,20 @@ -import { BOARD_NAMES } from "../../../../../constants/boards"; +import { useStore } from "../../../../../store/store"; import type { TelemetryCatalogItem } from "../../../../../types/data/telemetryCatalogItem"; import { Tab } from "../tabs/Tab"; import { TelemetryItem } from "../tabs/telemetry/TelemetryItem"; -export const TelemetrySection = () => ( - ( - - )} - virtualized - /> -); +export const TelemetrySection = () => { + const boards = useStore((s) => s.boards); + + return ( + ( + + )} + virtualized + /> + ); +}; diff --git a/frontend/testing-view/src/hooks/useBoardData.ts b/frontend/testing-view/src/hooks/useBoardData.ts index 22ff3d8f9..3f40f4cfc 100644 --- a/frontend/testing-view/src/hooks/useBoardData.ts +++ b/frontend/testing-view/src/hooks/useBoardData.ts @@ -90,6 +90,8 @@ export function useBoardData( logger.testingView.log("[useBoardData] Commands data processed"); + console.log("availableBoards", availableBoards); + return { telemetryCatalog: telemetryCatalogResult, commandsCatalog: commandsCatalogResult, diff --git a/frontend/testing-view/src/hooks/useTransformedBoards.ts b/frontend/testing-view/src/hooks/useTransformedBoards.ts index c8fc04c40..aa7cda62b 100644 --- a/frontend/testing-view/src/hooks/useTransformedBoards.ts +++ b/frontend/testing-view/src/hooks/useTransformedBoards.ts @@ -12,6 +12,7 @@ export function useTransformedBoards( const setTelemetryCatalog = useStore((s) => s.setTelemetryCatalog); const setCommandsCatalog = useStore((s) => s.setCommandsCatalog); + const setBoards = useStore((s) => s.setBoards); const initializeWorkspaceFilters = useStore( (s) => s.initializeWorkspaceFilters, ); @@ -25,7 +26,14 @@ export function useTransformedBoards( setTelemetryCatalog(transformedBoards.telemetryCatalog); setCommandsCatalog(transformedBoards.commandsCatalog); - initializeWorkspaceFilters(); + setBoards(Array.from(transformedBoards.boards)); + + const hasTelemetryData = + Object.keys(transformedBoards.telemetryCatalog).length > 0; + const hasCommandsData = + Object.keys(transformedBoards.commandsCatalog).length > 0; + + if (hasTelemetryData && hasCommandsData) initializeWorkspaceFilters(); }, [ transformedBoards, setTelemetryCatalog, diff --git a/frontend/testing-view/src/lib/utils.ts b/frontend/testing-view/src/lib/utils.ts index fe0306338..adbcdfc2f 100644 --- a/frontend/testing-view/src/lib/utils.ts +++ b/frontend/testing-view/src/lib/utils.ts @@ -1,5 +1,4 @@ import { ACRONYMS } from "../constants/acronyms"; -import { BOARD_NAMES } from "../constants/boards"; import { variablesBadgeClasses } from "../constants/variablesBadgeClasses"; import type { FilterScope, @@ -29,8 +28,8 @@ export const generateInitialFilters = ( ); }; -export const createEmptyFilter = (): TabFilter => { - return BOARD_NAMES.reduce((acc, category) => { +export const createEmptyFilter = (boards: BoardName[]): TabFilter => { + return boards.reduce((acc, category) => { acc[category] = []; return acc; }, {} as TabFilter); @@ -38,8 +37,9 @@ export const createEmptyFilter = (): TabFilter => { export const createFullFilter = ( dataSource: Record, + boards: BoardName[], ): TabFilter => { - return BOARD_NAMES.reduce((acc, category) => { + return boards.reduce((acc, category) => { acc[category] = dataSource[category]?.map((item) => item.id) || []; return acc; }, {} as TabFilter); diff --git a/frontend/testing-view/src/store/slices/catalogSlice.ts b/frontend/testing-view/src/store/slices/catalogSlice.ts index e2e60017f..dcd365534 100644 --- a/frontend/testing-view/src/store/slices/catalogSlice.ts +++ b/frontend/testing-view/src/store/slices/catalogSlice.ts @@ -15,6 +15,10 @@ export interface CatalogSlice { setTelemetryCatalog: ( telemetryCatalog: Record, ) => void; + + // Boards + boards: BoardName[]; + setBoards: (boards: BoardName[]) => void; } export const createCatalogSlice: StateCreator = ( @@ -24,4 +28,6 @@ export const createCatalogSlice: StateCreator = ( telemetryCatalog: {} as Record, setCommandsCatalog: (commandsCatalog) => set({ commandsCatalog }), setTelemetryCatalog: (telemetryCatalog) => set({ telemetryCatalog }), + boards: [] as BoardName[], + setBoards: (boards) => set({ boards }), }); diff --git a/packet-sender/package.json b/packet-sender/package.json index 4842b09d2..d96551e08 100644 --- a/packet-sender/package.json +++ b/packet-sender/package.json @@ -7,7 +7,6 @@ "scripts": { "build": "go build -o packet-sender main.go", "build:ci": "go build", - "dev": "go run main.go", "test": "go test ./..." } } From 8168f40a2d2420879ac3a0e7b9c650b858863108 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:28:37 +0100 Subject: [PATCH 05/18] feat: increase backend resolving time --- electron-app/src/processes/backend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron-app/src/processes/backend.js b/electron-app/src/processes/backend.js index 1e215619d..c994a94e3 100644 --- a/electron-app/src/processes/backend.js +++ b/electron-app/src/processes/backend.js @@ -86,7 +86,7 @@ function startBackend() { // If the backend didn't fail in this period of time, resolve the promise setTimeout(() => { resolve(backendProcess); - }, 1000); + }, 2000); // Handle process exit backendProcess.on("close", (code) => { From b6e9f00a79ce141cc3e843a34e8a7fc33c302cb7 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:37:34 +0100 Subject: [PATCH 06/18] fix: workflows --- .github/workflows/build.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2564f79db..37d11b4c4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -57,7 +57,6 @@ jobs: - uses: dorny/paths-filter@v3 id: filter with: - ref: "production" filters: | backend: - 'backend/**/*' From eb85a77697d21bc1e1ec7b7b6802f7a6a38d09bb Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:41:31 +0100 Subject: [PATCH 07/18] fix --- .github/workflows/build.yaml | 2 +- .../workspace/store/workspacesSlice.ts | 6 ++--- frontend/testing-view/src/lib/utils.test.ts | 24 +++++++++++++++++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 37d11b4c4..dbbfce409 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -112,7 +112,7 @@ jobs: with: workflow: build.yaml branch: production - workflow_conclusion: success + workflow_conclusion: completed name: backend-${{ matrix.platform }} path: backend/cmd diff --git a/frontend/testing-view/src/features/workspace/store/workspacesSlice.ts b/frontend/testing-view/src/features/workspace/store/workspacesSlice.ts index ea09a6844..28a1be861 100644 --- a/frontend/testing-view/src/features/workspace/store/workspacesSlice.ts +++ b/frontend/testing-view/src/features/workspace/store/workspacesSlice.ts @@ -63,9 +63,9 @@ export const createWorkspacesSlice: StateCreator< const newWorkspaceFilters = { ...state.workspaceFilters, [newWorkspaceId]: { - commands: createFullFilter(commands), - telemetry: createFullFilter(telemetry), - logs: createFullFilter(telemetry), + commands: createFullFilter(commands, get().boards), + telemetry: createFullFilter(telemetry, get().boards), + logs: createFullFilter(telemetry, get().boards), }, }; diff --git a/frontend/testing-view/src/lib/utils.test.ts b/frontend/testing-view/src/lib/utils.test.ts index 947b6c5c7..fa667af7a 100644 --- a/frontend/testing-view/src/lib/utils.test.ts +++ b/frontend/testing-view/src/lib/utils.test.ts @@ -105,7 +105,17 @@ describe("getTypeBadgeClass", () => { describe("emptyFilter", () => { it("should return the correct empty filter", () => { - expect(createEmptyFilter()).toStrictEqual({ + const boards = [ + "BCU", + "PCU", + "LCU", + "HVSCU", + "HVSCU-Cabinet", + "BMSL", + "VCU", + ]; + + expect(createEmptyFilter(boards)).toStrictEqual({ BCU: [], PCU: [], LCU: [], @@ -133,7 +143,17 @@ describe("fullFilter", () => { VCU: [], }; - expect(createFullFilter(testDataSource)).toStrictEqual({ + const boards = [ + "BCU", + "PCU", + "LCU", + "HVSCU", + "HVSCU-Cabinet", + "BMSL", + "VCU", + ]; + + expect(createFullFilter(testDataSource, boards)).toStrictEqual({ BCU: [1], PCU: [2], LCU: [3], From 47156f5f16bc67f45f3a6128db3cf37aa77b6882 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:54:52 +0100 Subject: [PATCH 08/18] Update build.yaml --- .github/workflows/build.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index dbbfce409..c6a335ac0 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -53,6 +53,8 @@ jobs: competition-view: ${{ steps.filter.outputs.competition-view == 'true' || github.event.inputs.rebuild-competition-view == 'true' || inputs.build-competition-view == true }} steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: dorny/paths-filter@v3 id: filter From 7ccfe4ba9907591c5ab0f1f09a1abe6327754ca2 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:56:16 +0100 Subject: [PATCH 09/18] check --- .../testing-view/src/features/workspace/store/workspacesSlice.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/testing-view/src/features/workspace/store/workspacesSlice.ts b/frontend/testing-view/src/features/workspace/store/workspacesSlice.ts index 28a1be861..191ab3c75 100644 --- a/frontend/testing-view/src/features/workspace/store/workspacesSlice.ts +++ b/frontend/testing-view/src/features/workspace/store/workspacesSlice.ts @@ -68,6 +68,7 @@ export const createWorkspacesSlice: StateCreator< logs: createFullFilter(telemetry, get().boards), }, }; + // test // Initialize expanded items for the new workspace const newExpandedItems = { From 913fe95a44b5a683694812720229852d20d03d47 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:01:40 +0100 Subject: [PATCH 10/18] Update build.yaml --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c6a335ac0..516ddbdc7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -59,6 +59,7 @@ jobs: - uses: dorny/paths-filter@v3 id: filter with: + base: ${{ github.event.before }} filters: | backend: - 'backend/**/*' From 322c199d92259d52570f0e9b2a9634f9e027d850 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:02:37 +0100 Subject: [PATCH 11/18] fix --- .../testing-view/src/features/workspace/store/workspacesSlice.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/testing-view/src/features/workspace/store/workspacesSlice.ts b/frontend/testing-view/src/features/workspace/store/workspacesSlice.ts index 191ab3c75..28a1be861 100644 --- a/frontend/testing-view/src/features/workspace/store/workspacesSlice.ts +++ b/frontend/testing-view/src/features/workspace/store/workspacesSlice.ts @@ -68,7 +68,6 @@ export const createWorkspacesSlice: StateCreator< logs: createFullFilter(telemetry, get().boards), }, }; - // test // Initialize expanded items for the new workspace const newExpandedItems = { From c55f0ac82508e3d521a45e01554d24d0a075ac5b Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Sat, 21 Feb 2026 20:43:31 +0100 Subject: [PATCH 12/18] feat: add building to frontend testing --- .github/workflows/frontend-tests.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/frontend-tests.yaml b/.github/workflows/frontend-tests.yaml index 149d955df..0599a5097 100644 --- a/.github/workflows/frontend-tests.yaml +++ b/.github/workflows/frontend-tests.yaml @@ -41,5 +41,8 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile --filter=testing-view --filter=ui --filter=core + - name: Build frontend + run: pnpm build --filter="./frontend/**" + - name: Run tests run: pnpm test --filter="./frontend/**" From 225cbe5a2c23df9abadea0b8b44aa34a146ad8b9 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Sat, 21 Feb 2026 20:54:13 +0100 Subject: [PATCH 13/18] feat: increase backend resolving time --- electron-app/src/processes/backend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron-app/src/processes/backend.js b/electron-app/src/processes/backend.js index c994a94e3..747336e3d 100644 --- a/electron-app/src/processes/backend.js +++ b/electron-app/src/processes/backend.js @@ -86,7 +86,7 @@ function startBackend() { // If the backend didn't fail in this period of time, resolve the promise setTimeout(() => { resolve(backendProcess); - }, 2000); + }, 4000); // Handle process exit backendProcess.on("close", (code) => { From 6840db855b67b7bf8560122fe930c48b1d800689 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:11:57 +0100 Subject: [PATCH 14/18] feat: include rpm and pacman distributives --- electron-app/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/electron-app/package.json b/electron-app/package.json index a37d20f36..30344aa16 100644 --- a/electron-app/package.json +++ b/electron-app/package.json @@ -106,7 +106,9 @@ "linux": { "target": [ "AppImage", - "deb" + "deb", + "rpm", + "pacman" ], "icon": "icons/512x512.png", "category": "Utility", From 4740414d812f30fca609c7989d171cdd12e88ac4 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:20:39 +0100 Subject: [PATCH 15/18] fix: include dependencies and new files --- .github/workflows/release.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index abcc3c225..01f375565 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -86,6 +86,12 @@ jobs: echo "Updated version to:" cat package.json | grep version + - name: Install Linux build dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y rpm libarchive-tools + # Download ONLY the appropriate backend for this platform - name: Download Linux backend if: runner.os == 'Linux' @@ -182,6 +188,8 @@ jobs: electron-app/dist/*.exe electron-app/dist/*.AppImage electron-app/dist/*.deb + electron-app/dist/*.rpm + electron-app/dist/*.pacman electron-app/dist/*.dmg electron-app/dist/*.zip electron-app/dist/*.yml From 33630b216013516a9d2cef649e40851ccd5cb713 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:30:59 +0100 Subject: [PATCH 16/18] Update README.md --- electron-app/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron-app/README.md b/electron-app/README.md index 854930745..a881da8e5 100644 --- a/electron-app/README.md +++ b/electron-app/README.md @@ -79,7 +79,7 @@ pnpm run dist:linux # Linux On macOS, the backend requires the loopback address `127.0.0.9` to be configured. If you encounter a "can't assign requested address" error when starting the backend, run: ``` -sudo ipconfig set en0 INFORM 127.0.0.9 +sudo ifconfig lo0 alias 127.0.0.9 up ``` ## Available Scripts From ee6441cdc57928ab8886e616febfdcbe9a49488f Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:33:43 +0100 Subject: [PATCH 17/18] Update README.md --- electron-app/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/electron-app/README.md b/electron-app/README.md index a881da8e5..7034c163e 100644 --- a/electron-app/README.md +++ b/electron-app/README.md @@ -89,6 +89,7 @@ sudo ifconfig lo0 alias 127.0.0.9 up - `pnpm start` - Run application in development mode - `pnpm run dist` - Build production executable - `pnpm test` - Run tests +- `pnpm build-icons` - build icon from the icon.png file in the `/electron-app` folder ...and many custom variations (see package.json) # Only works and makes sense after running `pnpm run dist` From ef5bd72490e123896ec9e82c74c66fedacbdd2e7 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:34:46 +0100 Subject: [PATCH 18/18] feat: add stale boards indication --- .../filtering/components/FilterController.tsx | 6 +++ .../filtering/components/FilterDialog.tsx | 30 ++++++++++- .../filtering/store/filteringSlice.ts | 3 +- .../rightSidebar/tabs/TabHeader.tsx | 53 +++++++++++++------ frontend/testing-view/src/lib/utils.ts | 8 +++ 5 files changed, 81 insertions(+), 19 deletions(-) diff --git a/frontend/testing-view/src/features/filtering/components/FilterController.tsx b/frontend/testing-view/src/features/filtering/components/FilterController.tsx index 538cef4a9..fdea53a8d 100644 --- a/frontend/testing-view/src/features/filtering/components/FilterController.tsx +++ b/frontend/testing-view/src/features/filtering/components/FilterController.tsx @@ -1,3 +1,5 @@ +import { useShallow } from "zustand/shallow"; +import { detectExtraBoards } from "../../../lib/utils"; import { useStore } from "../../../store/store"; import { FilterCategoryItem } from "./FilterCategoryItem"; import { FilterDialog } from "./FilterDialog"; @@ -7,12 +9,15 @@ export const FilterController = () => { const close = useStore((s) => s.closeFilterDialog); const boards = useStore((s) => s.boards); + const activeFilters = useStore(useShallow((s) => s.getActiveFilters(scope))); const clearFilters = useStore((s) => s.clearFilters); const selectAllFilters = useStore((s) => s.selectAllFilters); if (!scope) return null; + const extraBoards = detectExtraBoards(activeFilters, boards); + return ( { onClearAll={() => clearFilters(scope)} onSelectAll={() => selectAllFilters(scope)} categories={boards} + extraCategories={extraBoards} FilterCategoryComponent={FilterCategoryItem} /> ); diff --git a/frontend/testing-view/src/features/filtering/components/FilterDialog.tsx b/frontend/testing-view/src/features/filtering/components/FilterDialog.tsx index 6c72ba88c..f211b2b33 100644 --- a/frontend/testing-view/src/features/filtering/components/FilterDialog.tsx +++ b/frontend/testing-view/src/features/filtering/components/FilterDialog.tsx @@ -6,6 +6,7 @@ import { DialogHeader, DialogTitle, } from "@workspace/ui"; +import { AlertTriangle } from "@workspace/ui/icons"; import { type ComponentType } from "react"; import type { BoardName } from "../../../types/data/board"; @@ -17,6 +18,7 @@ interface FilterDialogProps { onClearAll: () => void; onSelectAll: () => void; categories: readonly BoardName[]; + extraCategories: readonly BoardName[]; FilterCategoryComponent: ComponentType<{ category: BoardName }>; } @@ -28,11 +30,13 @@ export const FilterDialog = ({ onClearAll, onSelectAll, categories, + extraCategories, FilterCategoryComponent, }: FilterDialogProps) => { + console.log(extraCategories); return ( - + {title} {description && {description}} @@ -47,6 +51,30 @@ export const FilterDialog = ({
+ {extraCategories.length > 0 && ( +
+
+ + Stale filters detected +
+

+ The following boards are in your saved filters but not in the + current configuration:{" "} + + {extraCategories.join(", ")} + +

+ +
+ )} +
{categories.map((category) => ( diff --git a/frontend/testing-view/src/features/filtering/store/filteringSlice.ts b/frontend/testing-view/src/features/filtering/store/filteringSlice.ts index ecb047379..58cefaccc 100644 --- a/frontend/testing-view/src/features/filtering/store/filteringSlice.ts +++ b/frontend/testing-view/src/features/filtering/store/filteringSlice.ts @@ -38,7 +38,7 @@ export interface FilteringSlice { workspaceFilters: Record; initializeWorkspaceFilters: () => void; updateFilters: (scope: FilterScope, filters: TabFilter) => void; - getActiveFilters: (scope: FilterScope) => TabFilter | undefined; + getActiveFilters: (scope: FilterScope | null) => TabFilter | undefined; /** Filter Actions */ selectAllFilters: (scope: FilterScope) => void; @@ -229,6 +229,7 @@ export const createFilteringSlice: StateCreator< // Helper getters getActiveFilters: (scope) => { const id = get().getActiveWorkspaceId(); + if (!scope) return {}; return id ? get().workspaceFilters[id]?.[scope] : undefined; }, diff --git a/frontend/testing-view/src/features/workspace/components/rightSidebar/tabs/TabHeader.tsx b/frontend/testing-view/src/features/workspace/components/rightSidebar/tabs/TabHeader.tsx index 08730d14c..e93c10110 100644 --- a/frontend/testing-view/src/features/workspace/components/rightSidebar/tabs/TabHeader.tsx +++ b/frontend/testing-view/src/features/workspace/components/rightSidebar/tabs/TabHeader.tsx @@ -1,5 +1,7 @@ import { Button } from "@workspace/ui"; -import { ListFilterPlus } from "@workspace/ui/icons"; +import { AlertTriangle, ListFilterPlus } from "@workspace/ui/icons"; +import { useShallow } from "zustand/shallow"; +import { detectExtraBoards } from "../../../../../lib/utils"; import { useStore } from "../../../../../store/store"; import type { SidebarTab } from "../../../types/sidebar"; @@ -13,23 +15,40 @@ export const TabHeader = ({ title, scope }: TabHeaderProps) => { const totalCount = useStore((state) => state.getTotalCount(scope)); const filteredCount = useStore((state) => state.getFilteredCount(scope)); + const boards = useStore((s) => s.boards); + const activeFilters = useStore(useShallow((s) => s.getActiveFilters(scope))); + const extraBoards = detectExtraBoards(activeFilters, boards); + return ( -
-

- {title} - - {filteredCount} / {totalCount} - -

- +
+
+

+ {title} + + {filteredCount} / {totalCount} + +

+ +
+ + {/* Warning for stale boards */} + {extraBoards.length > 0 && ( +
+ + {extraBoards.length} stale board(s) affecting counts +
+ )}
); }; diff --git a/frontend/testing-view/src/lib/utils.ts b/frontend/testing-view/src/lib/utils.ts index adbcdfc2f..0df30e9c3 100644 --- a/frontend/testing-view/src/lib/utils.ts +++ b/frontend/testing-view/src/lib/utils.ts @@ -119,3 +119,11 @@ export const formatTimestamp = (ts: MessageTimestamp) => { if (!ts) return "00:00:00"; return `${ts.hour.toString().padStart(2, "0")}:${ts.minute.toString().padStart(2, "0")}:${ts.second.toString().padStart(2, "0")}`; }; + +export const detectExtraBoards = ( + activeFilters: TabFilter | undefined, + boards: BoardName[], +) => + Object.keys(activeFilters || {}).filter( + (key) => !boards.includes(key), + ) as BoardName[];