Skip to content
Open
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
101 changes: 101 additions & 0 deletions .github/workflows/build-and-deploy-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Build and Deploy to Dev

on:
push:
branches:
- main

permissions:
contents: write
deployments: write
actions: read

jobs:
build:
runs-on: ubuntu-22.04
outputs:
rpm_name: ${{ steps.find-rpm.outputs.rpm_name }}
image_tag: ${{ steps.vars.outputs.image_tag }}
steps:
- name: Checkout codebase
uses: actions/checkout@v4

- name: Set image tag
id: vars
run: echo "image_tag=dev-${GITHUB_SHA::7}" >> $GITHUB_OUTPUT

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
run_install: false

- name: Install pnpm dependencies with caching
uses: actions/setup-node@v4
with:
node-version: 23.11.0
cache: pnpm
- run: pnpm install

- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.93.1

- name: Install dependencies (ubuntu only)
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev

- name: Install Tauri
run: pnpm add -D @tauri-apps/cli@1

- name: Configure Tauri
run: |
pnpm tauri init \
--app-name "Squirrel" \
--window-title "Squirrel" \
--dist-dir ../dist \
--dev-path http://localhost:5173 \
--before-dev-command "pnpm dev" \
--before-build-command "pnpm build"

- name: Build Tauri package
id: build
uses: tauri-apps/tauri-action@v0.6.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: >-
--bundles rpm
--config {"tauri":{"bundle":{"identifier":"edu.stanford.slac.squirrel"},"allowlist":{"fs":{"readFile":true,"scope":["$RESOURCE/*"]},"path":{"all":true}}}}

- name: Find RPM file
id: find-rpm
run: |
RPM_PATH=$(ls src-tauri/target/release/bundle/rpm/*.rpm)
RPM_NAME=$(basename "$RPM_PATH")
echo "rpm_name=$RPM_NAME" >> $GITHUB_OUTPUT
echo "rpm_path=$RPM_PATH" >> $GITHUB_OUTPUT
echo "Found RPM: $RPM_NAME"

- name: Create/update dev-latest pre-release
uses: softprops/action-gh-release@v2
with:
tag_name: dev-latest
name: Dev Latest
prerelease: true
files: ${{ steps.find-rpm.outputs.rpm_path }}
body: |
Rolling dev build from main branch.
Commit: ${{ github.sha }}
Built: ${{ github.event.head_commit.timestamp }}

deploy:
needs: build
uses: ad-build-test/build-system-playbooks/.github/workflows/request-deployment.yml@main
with:
deploy_to_dev: true
tag: ${{ needs.build.outputs.image_tag }}
deployment_type: app
artifact_url: ${{ github.server_url }}/${{ github.repository }}/releases/download/dev-latest/${{ needs.build.outputs.rpm_name }}
artifact_type: rpm
65 changes: 65 additions & 0 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Build Release Artifact

on:
release:
types: [published]

permissions:
contents: write

jobs:
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout codebase
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
run_install: false

- name: Install pnpm dependencies with caching
uses: actions/setup-node@v4
with:
node-version: 23.11.0
cache: pnpm
- run: pnpm install

- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.93.1

- name: Install dependencies (ubuntu only)
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev

- name: Install Tauri
run: pnpm add -D @tauri-apps/cli@1

- name: Configure Tauri
run: |
pnpm tauri init \
--app-name "Squirrel" \
--window-title "Squirrel" \
--dist-dir ../dist \
--dev-path http://localhost:5173 \
--before-dev-command "pnpm dev" \
--before-build-command "pnpm build"

- name: Build Tauri package
id: build
uses: tauri-apps/tauri-action@v0.6.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: >-
--bundles rpm
--config {"tauri":{"bundle":{"identifier":"edu.stanford.slac.squirrel"},"allowlist":{"fs":{"readFile":true,"scope":["$RESOURCE/*"]},"path":{"all":true}}}}

- name: Attach RPM to release
uses: softprops/action-gh-release@v2
with:
files: src-tauri/target/release/bundle/rpm/*.rpm
60 changes: 60 additions & 0 deletions .github/workflows/deploy-prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Deploy to Production

on:
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag to deploy (e.g., v1.0.0)'
required: true
type: string
deploy_to_lcls:
description: 'Deploy to LCLS'
required: false
type: boolean
default: true
deploy_to_facet:
description: 'Deploy to FACET'
required: false
type: boolean
default: false
deploy_to_testfac:
description: 'Deploy to TESTFAC'
required: false
type: boolean
default: false

permissions:
deployments: write
contents: read
actions: read

jobs:
find-rpm:
runs-on: ubuntu-latest
outputs:
rpm_name: ${{ steps.find.outputs.rpm_name }}
steps:
- name: Find RPM asset in release
id: find
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RPM_NAME=$(gh release view "${{ inputs.release_tag }}" --repo "${{ github.repository }}" --json assets --jq '.assets[] | select(.name | endswith(".rpm")) | .name')
if [ -z "$RPM_NAME" ]; then
echo "Error: No RPM found in release ${{ inputs.release_tag }}"
exit 1
fi
echo "rpm_name=$RPM_NAME" >> $GITHUB_OUTPUT
echo "Found RPM: $RPM_NAME"

deploy:
needs: find-rpm
uses: ad-build-test/build-system-playbooks/.github/workflows/request-deployment.yml@main
with:
deploy_to_lcls: ${{ inputs.deploy_to_lcls }}
deploy_to_facet: ${{ inputs.deploy_to_facet }}
deploy_to_testfac: ${{ inputs.deploy_to_testfac }}
tag: ${{ inputs.release_tag }}
deployment_type: app
artifact_url: ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ inputs.release_tag }}/${{ needs.find-rpm.outputs.rpm_name }}
artifact_type: rpm
5 changes: 4 additions & 1 deletion .github/workflows/tauri.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ jobs:
--before-dev-command "pnpm dev" \
--before-build-command "pnpm build"

- name: Pin tauri crate to version with fixed wry/webkit2gtk compatibility
run: sed -i 's/^tauri = .*/tauri = { version = "1.8", features = ["shell-open"] }/' src-tauri/Cargo.toml

- name: Build Tauri package
id: build
uses: tauri-apps/tauri-action@v0.6.1
Expand All @@ -66,7 +69,7 @@ jobs:
with:
args: >-
${{ matrix.args }}
--config {"tauri":{"bundle":{"identifier":"edu.stanford.slac.squirrel"}}}
--config {"tauri":{"bundle":{"identifier":"edu.stanford.slac.squirrel"},"allowlist":{"shell":{"open":true}}}}

- name: Upload Tauri artifacts
uses: actions/upload-artifact@v6
Expand Down
3 changes: 3 additions & 0 deletions config.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"apiUrl": "http://localhost:8080"
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@tanstack/react-router": "^1.168.3",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.13.23",
"@tauri-apps/api": "^1.6.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"socket.io-client": "^4.8.3"
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,20 @@ export function Layout({ children }: LayoutProps) {
setSnapshotDialogOpen(true);
};

const openExternal = (url: string) => {
if ('__TAURI__' in window) {
import('@tauri-apps/api/shell').then(({ open }) => open(url));
} else {
window.open(url, '_blank');
}
};

const handleHelpClick = () => {
window.open('https://github.com/slaclab/squirrel', '_blank');
openExternal('https://slaclab.github.io/react-squirrel/');
};

const handleBugReportClick = () => {
window.open('https://forms.office.com/r/A6p1TmFNw3', '_blank');
openExternal('https://forms.office.com/r/A6p1TmFNw3');
};

return (
Expand Down
53 changes: 48 additions & 5 deletions src/config/api.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
/**
* API configuration for score-backend
* Based on configuration from superscore/squirrel-local.cfg
* API configuration for squirrel-backend
*
* Note: baseURL is empty because Vite proxy handles forwarding /v1/* to http://localhost:8080
* In dev mode, Vite proxy forwards /v1/* to http://localhost:8080.
* In production (Tauri), config.json is read at startup to determine the backend URL.
* Falls back to VITE_API_URL env var if config.json is not available.
*/

let runtimeBaseURL: string | null = null;

/**
* Load runtime configuration from config.json via Tauri FS API.
* Must be called before the app renders. No-ops in non-Tauri environments.
*/
export async function loadRuntimeConfig(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, no-underscore-dangle
if (!(window as any).__TAURI__) return;

try {
const { resolveResource } = await import('@tauri-apps/api/path');
const { readTextFile } = await import('@tauri-apps/api/fs');

const configPath = await resolveResource('config.json');
const text = await readTextFile(configPath);
const config = JSON.parse(text);
if (config.apiUrl) {
runtimeBaseURL = config.apiUrl;
}
} catch (e) {
// eslint-disable-next-line no-console
console.warn('Could not load config.json, using build-time fallback');
}
}

export const API_CONFIG = {
baseURL: '', // Empty to use Vite proxy
get baseURL(): string {
return runtimeBaseURL || import.meta.env.VITE_API_URL || '';
},
endpoints: {
snapshots: '/v1/snapshots',
pvs: '/v1/pvs',
Expand All @@ -18,7 +47,21 @@ export const API_CONFIG = {
};

/**
* Generic API response wrapper from score-backend
* Derive a WebSocket URL from the configured base URL, or fall back to
* the current page host (for Vite proxy in dev mode).
*/
export function getWebSocketURL(path: string): string {
if (API_CONFIG.baseURL) {
const url = new URL(API_CONFIG.baseURL);
const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
return `${protocol}//${url.host}${path}`;
}
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
return `${protocol}//${window.location.host}${path}`;
}

/**
* Generic API response wrapper from squirrel-backend
*/
export interface ApiResultResponse<T> {
errorCode: number;
Expand Down
2 changes: 1 addition & 1 deletion src/contexts/LivePVContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function LivePVProvider({

try {
// Use POST to avoid URL length limits with many PVs
const response = await fetch(`${API_CONFIG.endpoints.pvs}/live`, {
const response = await fetch(`${API_CONFIG.baseURL}${API_CONFIG.endpoints.pvs}/live`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand Down
Loading
Loading