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
40 changes: 40 additions & 0 deletions .github/workflows/dashboard-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Dashboard CI

on:
push:
branches: [main]
paths: ["dashboard/**"]
pull_request:
branches: [main]
paths: ["dashboard/**"]

defaults:
run:
working-directory: dashboard

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: dashboard/package-lock.json

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Typecheck
run: npm run typecheck

- name: Build
run: npm run build
env:
VITE_SUPABASE_URL: https://placeholder.supabase.co
VITE_SUPABASE_ANON_KEY: placeholder-key
56 changes: 56 additions & 0 deletions .github/workflows/deploy-dashboard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Deploy Dashboard

on:
push:
tags: ["dashboard-v*"]
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

defaults:
run:
working-directory: dashboard

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: dashboard/package-lock.json

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build
env:
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}

- name: Upload pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: dashboard/dist

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,13 @@ Thumbs.db
*.bin
*.hex
*.elf

# Dashboard
dashboard/node_modules/
dashboard/dist/

# Environment files
.env
.env.local
.env.*.local
/configs/supabase.env
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ benchctl gpio get --pin fault --target samd51
benchctl health
```

## Status Dashboard

Optional Supabase-backed remote monitoring with a GitHub Pages dashboard. Each bench publishes health status and heartbeats; authorized users can view all benches from anywhere.

See [docs/dashboard-setup.md](docs/dashboard-setup.md) for setup instructions.

## CLI Reference

```
Expand All @@ -48,6 +54,7 @@ benchctl [--config PATH] [--verbose] [--dry-run]
gpio set|get|pulse --pin NAME|NUM --value high|low
health [--json]
config show|validate|generate
publish status|heartbeat|config
```

## CI Integration
Expand Down Expand Up @@ -84,9 +91,13 @@ src/hilbench/ Python package
health.py Health check logic
artifacts.py Firmware artifact resolution
cli/ Click CLI commands
publisher/ Supabase status publisher (optional)
bootstrap/ Pi provisioning scripts
configs/ Config templates
systemd/ Health check timer
systemd/ Health check timer + publisher service
dashboard/ React status dashboard (Vite + Pico CSS)
supabase/migrations/ Database schema SQL files
docs/ Setup and architecture docs
udev/ Device permission rules
```

Expand Down
11 changes: 10 additions & 1 deletion bootstrap/bootstrap_pi.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

set -euo pipefail

BENCH_NAME="${1:?Usage: $0 <bench-name> [github-org-token]}"
BENCH_NAME="${1:?Usage: $0 <bench-name> [github-org-token] [supabase-url] [supabase-key]}"
GITHUB_TOKEN="${2:-}"
SUPABASE_URL="${3:-}"
SUPABASE_KEY="${4:-}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_DIR="$(dirname "$SCRIPT_DIR")"

Expand All @@ -31,6 +33,13 @@ else
echo "Run manually: sudo ${SCRIPT_DIR}/install_runner.sh $BENCH_NAME <token>"
fi

if [[ -n "$SUPABASE_URL" ]] && [[ -n "$SUPABASE_KEY" ]]; then
"${SCRIPT_DIR}/install_publisher.sh" "$REPO_DIR" "$SUPABASE_URL" "$SUPABASE_KEY"
else
echo "--- Skipping publisher install (no Supabase URL/key provided) ---"
echo "Run manually: sudo ${SCRIPT_DIR}/install_publisher.sh $REPO_DIR <url> <key>"
fi

echo ""
echo "=== Bootstrap complete for ${BENCH_NAME} ==="
echo "Config: /etc/hil-bench/config.yaml"
Expand Down
73 changes: 73 additions & 0 deletions bootstrap/install_publisher.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Install the Supabase publisher and heartbeat service.
# Idempotent — safe to re-run.
#
# Usage: sudo ./install_publisher.sh <repo-dir> [supabase-url] [supabase-key]
set -euo pipefail

REPO_DIR="${1:?Usage: $0 <repo-dir> [supabase-url] [supabase-key]}"
SUPABASE_URL="${2:-}"
SUPABASE_KEY="${3:-}"
VENV="/opt/hil-bench/venv"
ENV_FILE="/etc/hil-bench/supabase.env"
CONFIG_FILE="/etc/hil-bench/config.yaml"
SYSTEMD_DST="/etc/systemd/system"

echo "--- Installing Supabase publisher ---"

# Install supabase package into venv
if [[ -d "$VENV" ]]; then
echo "Installing supabase package..."
"${VENV}/bin/pip" install --quiet "supabase>=2.11"
else
echo "WARNING: venv not found at $VENV — run install_python_env.sh first"
fi

# Generate credentials file if URL/key provided and file doesn't exist
if [[ -n "$SUPABASE_URL" ]] && [[ -n "$SUPABASE_KEY" ]] && [[ ! -f "$ENV_FILE" ]]; then
# Read bench_name from existing config
BENCH_NAME=""
if [[ -f "$CONFIG_FILE" ]]; then
BENCH_NAME=$(grep -m1 'bench_name:' "$CONFIG_FILE" | awk '{print $2}' || true)
fi

echo "Generating ${ENV_FILE}..."
mkdir -p "$(dirname "$ENV_FILE")"
cat > "$ENV_FILE" <<ENVEOF
# Supabase credentials for HIL bench publisher
SUPABASE_URL=${SUPABASE_URL}
SUPABASE_KEY=${SUPABASE_KEY}
BENCH_EMAIL=${BENCH_NAME}@hil-bench.local
BENCH_PASSWORD=CHANGE_ME
ENVEOF
chmod 600 "$ENV_FILE"
echo "Created ${ENV_FILE} — edit BENCH_EMAIL and BENCH_PASSWORD"
elif [[ -f "$ENV_FILE" ]]; then
echo "Credentials file exists: ${ENV_FILE} (skipping)"
fi

# Install systemd unit
UNIT_SRC="${REPO_DIR}/systemd/hil-bench-publisher.service"
if [[ -f "$UNIT_SRC" ]]; then
cp "$UNIT_SRC" "${SYSTEMD_DST}/hil-bench-publisher.service"
systemctl daemon-reload
echo "Installed: ${SYSTEMD_DST}/hil-bench-publisher.service"

if [[ -f "$ENV_FILE" ]]; then
systemctl enable hil-bench-publisher.service
echo "Publisher service enabled (start with: systemctl start hil-bench-publisher)"
else
echo "Publisher service installed but NOT enabled (no credentials file)"
fi
else
echo "WARNING: Service file not found: $UNIT_SRC"
fi

echo ""
echo "Next steps:"
echo " 1. Create a bench user in Supabase Auth dashboard"
echo " - Set email/password matching ${ENV_FILE}"
echo " - Add user_metadata: {\"bench_name\": \"<your-bench-name>\"}"
echo " 2. Edit ${ENV_FILE} with correct credentials"
echo " 3. Start the service: sudo systemctl start hil-bench-publisher"
echo "--- Publisher install done ---"
12 changes: 12 additions & 0 deletions configs/supabase.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Supabase credentials for HIL bench publisher.
# Copy to /etc/hil-bench/supabase.env and fill in values.
# Create a bench user in Supabase Auth with bench_name in user_metadata.

SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your-anon-key
BENCH_EMAIL=bench-name@your-domain.com
BENCH_PASSWORD=secure-password-here

# Optional overrides
# HEARTBEAT_INTERVAL_S=60
# PUBLISH_EVENTS=false
2 changes: 2 additions & 0 deletions dashboard/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
17 changes: 17 additions & 0 deletions dashboard/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import js from "@eslint/js";
import reactHooks from "eslint-plugin-react-hooks";
import tseslint from "typescript-eslint";

export default tseslint.config(
{ ignores: ["dist"] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ["**/*.{ts,tsx}"],
plugins: {
"react-hooks": reactHooks,
},
rules: {
...reactHooks.configs.recommended.rules,
},
},
);
12 changes: 12 additions & 0 deletions dashboard/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HIL Bench Dashboard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading