Architecture
WEISS is split into three main services: a React front-end for user interface and widget rendering,
a FastAPI back-end for handling OPI contents, authentication and git interaction, and a dedicated
EPICS WebSocket bridge. The block diagram below summarizes their interactions:
The next sections have a more detailed breakdown of the internal structure of each service and their
interactions.
NGINX
NGINX (“engine-x”) serves as a reverse proxy for all incoming traffic. It
routes the browser requests to either the frontend, backend API or the EPICS WebSocket internal
ports as needed. Behind NGINX all services run under plain HTTP, being the NGINX layer responsible
for TLS termination, HTTPS support and routing.
Frontend
The frontend is a single-page React application that operates mainly in two modes: editing
(available for developer role only, see User Roles), and runtime.
In edit mode the user can create and modify widgets on the canvas, while in runtime mode the user
can only interact with the widgets but not change their properties or layout.
Both modes share the same core components. When in runtime, however, the connection to the EPICS
WebSocket is opened, and widgets are rendered through a separate component through which PV Data
traffic is injected.
Core shared frontend components:
For both modes, the operation and state management of the editor is handled through a combination of
React context providers and custom hooks. For pv state update handling,
Zustand is used.
Important
These are not all the shared elements of the frontend composition, but the more important ones.
Starting by these will naturally guide you through the other related files.
State management layer
WidgetContext (useWidgetManager) - owns the canonical list of widgets on the canvas. It
handles selection, undo/redo history, clipboard, grouping, and serialisation/deserialisation of
OPI files.
UIContext (useUIManager) - owns global UI state: the current mode (edit vs. runtime), the
open file, repository and authentication state, and any cross-cutting user interactions such as
loading or saving a file.
EpicsWSContext (useEpicsWS) - exposes the WebSocket connection state and lifecycle methods
(wsConnected, startNewSession, stopSession). It does not carry live PV data — PV values
are served by the Zustand pvStore described below.
WSActionsContext (useEpicsWS) - exposes only writePVValue. Backed by the same
useEpicsWS hook but kept as a separate context so write-only widgets do not re-render on
connection state changes.
Live PV data is stored in a Zustand module-level store (usePVStore,
src/services/pvStore.ts). useEpicsWS collects incoming WebSocket messages in a buffer and
flushes them into the store via requestAnimationFrame, capping updates at the user’s monitor
update rate. Widget components subscribe with a PV-specific selector:
const pvData = usePVStore((state) => state.pvs["MY:PV"]);
This means each widget re-renders only when its own PV changes, with no re-render propagation
through React context.
Note
The state management layer is the brains of the application. All global UI states and behaviors pass
through one of the above.
Rendering layer
GridZoneComp - the main editor canvas. It handles drag-and-drop of new widgets, panning,
zooming, and keyboard shortcuts. It is itself registered as a widget so that its editable
properties (background, grid size, macros, …) are managed through the same widget pipeline as
every other widget.
WidgetRenderer - iterates the widget list and renders each widget inside a resizable and
draggable container (react-rnd). It either renders the static component directly (editMode), or
delegates it to LiveWidget if in runtime.
LiveWidget - operates in runtime mode only. Subscribes to usePVStore with a PV-specific
selector (using useShallow for reference equality), then calls applyWidgetPVData()
(widgetRenderUtils.ts) to merge PV data, evaluate configured rules, and resolve macros before
rendering the widget component.
Runtime rule semantics are property-oriented: each rule targets one property and contains ordered
condition rulesets. If multiple rulesets match in the same rule, the last match wins; if multiple
rules target the same property, later rules in the list win. Legacy action-map rule payloads from
older .opi.json files (WEISS < 2.0.0) are still accepted on import and converted during load.
Services
APIClient - auto-generated TypeScript client produced by @hey-api/openapi-ts from the
FastAPI OpenAPI schema. Provides type-safe wrappers for all backend REST endpoints (auth, repo
CRUD, file read/write, Git operations).
Warning
Do not edit this folder manually. Regenerate it with pnpm exec openapi-ts after any
backend endpoint changes (API must be running on :8000).
AuthService - singleton (authService) that drives the full OAuth login lifecycle. Handles
provider redirect, code exchange, session restoration on page load (restoreSession()), and
logout. Broadcasts auth state changes to React via a callback observer consumed by useUIManager.
Dialog - imperative confirmation dialog. Call confirmDialog(options) from anywhere in the
app to open a modal; it returns a Promise<boolean> resolved when the user confirms or cancels.
Note
<DialogService /> must be mounted once in the component tree (currently in App.tsx)
to register the underlying handler. The same applies to <NotificationService /> below.
Notifications - fire-and-forget toast notifications. Call notifyUser(message, severity?)
to display a 4-second auto-dismiss snackbar. Accepted severity values: "success", "info",
"warning", "error".
WSClient - stateful WebSocket class injected into useEpicsWS. Manages
subscribe/unsubscribe/write messaging to the EPICS bridge, auto-reconnects with exponential
backoff on unexpected disconnection, and decodes base64-encoded binary arrays before data reaches
pvStore.
pvStore - Zustand store for live EPICS PV data. See the
State management layer section above for a full description.
Backend
API
The FastAPI back-end exposes three route groups:
Developer routes (/repos/staging): developer-only endpoints (enforced by
require_developer) that manage per-user editable copies of OPI repositories. Each registered
repository is cloned as a bare Git repository; every developer who opens it gets an isolated git
worktree created automatically for their user ID. This means concurrent edits from different
users never interfere with each other. Commits, tags, file CRUD, and deployments are all driven
through these worktrees via their respective endpoints. For interaction with remote, the Technical
Account Token is used. See Git Interaction for more details.
Operator routes (/repos/deployed): read-only endpoints used by the frontend to serve the
currently deployed OPI snapshot to “operator” users.
Snapshot routes (/repos/snapshot): provides storage interaction with PV snapshots
(saving to disk, fetching values, etc.). These endpoints DO NOT communicate to PVs in any
form, i.e., you need to provide the data to be saved, and for restoring, you need to restore it
yourself (WEISS frontend does that). This will change as the snapshot feature becomes a standalone
service.
Authentication
Authentication is based on OAuth 2.0 / OpenID Connect and is available through the /auth route
group. The backend loads the configured non-demo provider from api.auth.providers using
AUTH_IDENTITY_PROVIDER (default oauth) and also supports a dedicated demo provider when
DEMO_MODE=true. See Organization credentials for more details.
EPICS WebSocket bridge (epicsWS)
epicsWS is a standalone Python WebSocket service that sits between the frontend and the EPICS
IOCs. The frontend connects to it through WSClient and subscribes to PV names based on the widgets
present on the canvas.
The PVA library used is p4p, see
backend/epicsWS/PVAClient.
The CA library used is PyEpics, see
backend/epicsWS/CAClient.
The web socket application and connection manager can be seen in backend/epicsWS/epicsWS. The
concept was based on ORNL PV Web Socket (PVWS).
How it works
Based on the default protocol (see Environment Variables) or the
channel prefix of the PV names (e.g pva:// or ca://), the WS chooses the correct provider.
The incoming messages from all origins are parsed through a common interface defined on pvParser
source file. This results in a standard structure in the format of the PVData class, regardless of
the origin of the message.
This class was based on the
EPICS Normative Types
(with minor modifications for convenience). This way, a known format is always used, and the
front-end client only needs to know one data structure for all protocols. Similar to PVWS, extra
fields were added for base64 encoding for arrays, improving JSON data traffic. A separate field
for enumeration strings for enum/enum-like records was also added.
This service is intentionally isolated from the API: it has no dependency on authentication or
repository management and can be deployed and scaled independently.
Further tests on performance and scalability are planned for the near future.