

# Welcome to CitrineOS Operator UI
**CitrineOS Operator UI** is the web-based operator interface for **CitrineOS**, an open-source, modular backend
platform for managing Electric Vehicle (EV) charging infrastructure.
This repository contains the **Operator-facing User Interface** and related tooling.
It is designed to work together with **CitrineOS Core**, which provides the charging station management logic,
OCPP message handling, persistence layer, and GraphQL APIs.
> ⚠️ **Important:**
> The Operator UI **does not run standalone**.
> A running instance of **CitrineOS Core** (including Hasura GraphQL Engine) is required.
This app is one workspace member of the `citrineos-core` pnpm monorepo. For repository-wide setup and for running the
full stack (server + UI + Hasura) together, see the [root README](../../README.md).
---
## Table of Contents
- [Overview](#overview)
- [Architecture & Data Flow](#architecture--data-flow)
- [Prerequisites](#prerequisites)
- [Getting Started](#getting-started)
- [Running with Docker](#running-with-docker-recommended)
- [Running with pnpm (Local Development)](#running-with-pnpm-local-development)
- [Working with `@citrineos/base`](#working-with-citrineosbase)
- [Build & Development Workflow](#build--development-workflow)
- [Running the Application](#running-the-application)
- [Bringing a Charging Station Online (End-to-End)](#bringing-a-charging-station-online-end-to-end)
- [Create a Location](#create-a-location)
- [Add a Charging Station (Charge Point)](#add-a-charging-station-charge-point)
- [Add an EVSE (Electric Vehicle Supply Equipment)](#add-an-evse-electric-vehicle-supply-equipment)
- [Add a Connector](#add-a-connector)
- [Add an Authorization (ID Token)](#add-an-authorization-id-token)
- [Start the Charging Station (EVerest)](#start-the-charging-station-everest)
- [Usage](#usage)
- [Hasura Authentication](#hasura-authentication)
- [Related Documentation](#related-documentation)
- [Contributing](#contributing)
- [Licensing](#licensing)
- [Support & Contact](#support--contact)
---
## Overview
The CitrineOS Operator UI provides operators with:
- Visibility into charging stations, connectors, sessions, and transactions
- Operational controls via CitrineOS Core Message APIs
- Data access through **Hasura GraphQL Engine**
- A modern React-based UI built with **Refine**
The UI consumes:
- **Hasura GraphQL APIs** for data access
- **CitrineOS Core Message APIs** for sending commands to charging stations
- **CitrineOS Core Data APIs** for managing entities
---
## Architecture & Data Flow
```text
+-------------------+ GraphQL +----------------------+
| | <----------------> | |
| Operator UI | | Hasura GraphQL |
| | | Engine |
| | | |
+-------------------+ +----------+-----------+
|
| SQL
|
+---------v-----------+
| |
| PostgreSQL |
| |
+---------------------+
+-------------------+ REST +--------------------------+
| Operator UI | <--------------> | CitrineOS Core |
| | | (OCPP 1.6 & 2.0.1) |
+-------------------+ +--------------------------+
```
## Prerequisites
Before starting, ensure the following tools and services are installed and running.
- **Node.js** (v24.16.0 or higher)
[Link](https://nodejs.org)
- **pnpm**
[Link](https://pnpm.io/installation) — the workspace's package manager
- **Docker & Docker Compose** (recommended)
[Link](https://www.docker.com/products/docker-desktop/)
The following services must be available before starting the application:
- **CitrineOS Core**
Backend service responsible for OCPP 1.6 and OCPP 2.0.1 handling, charging station management, and APIs.
- **Hasura GraphQL Engine**
GraphQL layer used for data access and management.
References:
- **CitrineOS Core Repository**
[Documentation](https://github.com/citrineos/citrineos-core)
- **Hasura Documentation**
[Documentation](https://hasura.io/docs/2.0/)
## Getting Started
### Running with Docker (Recommended)
If **CitrineOS Core** is already running via Docker, the Operator UI can be started with a single command:
```bash
docker compose up -d
```
This will:
- Build the Operator UI container
- Start the UI connected to the configured Hasura and Core service
### Running with pnpm (Local Development)
The Operator UI lives inside the `citrineos-core` **pnpm workspace** (under `apps/operator-ui`). Install all
workspace dependencies once from the repository root, then start the dev server:
```bash
# from the repository root
pnpm install
# then, from apps/operator-ui
pnpm run dev
```
This starts the development server (Next.js + Refine) with hot reload.
### Working with `@citrineos/base`
The Operator UI depends on `@citrineos/base` via a `workspace:*` dependency, so pnpm resolves it from the local
`packages/base` automatically — no `npm link` step is required. Just build the workspace packages so the compiled
output is available:
```bash
# from the repository root
pnpm run build
```
## Build & Development Workflow
The Operator UI is a member of the `citrineos-core` **pnpm workspace**.
### Typical Workflow
1. Install and build the workspace from the repository root:
```bash
# from the repository root
pnpm install
pnpm run build
```
2. Start the dev server:
```bash
# from apps/operator-ui
pnpm run dev
```
## Running the Application
### Development Mode
```bash
pnpm run dev
```
- Runs the app with hot reload
- Default URL: http://localhost:3000
### Production Build
```bash
pnpm run build
pnpm run start
```
- Production URL: http://localhost:3000
## Bringing a Charging Station Online (End-to-End)
This section describes the **minimum operational steps** required to bring a charging station online using:
- **CitrineOS Operator UI**
- **CitrineOS Core**
- **EVerest simulator**
These steps are required even for local development and testing.
---
### Create a Location
In CitrineOS, **all charging stations must belong to a Location**.
1. Open **CitrineOS Operator UI**:
2. Navigate to:
```pgsql
Locations → Create Location
```
3. Fill in the required metadata (name, address, country, etc.)
4. Save the Location
> A Location represents a physical site such as a parking garage, depot, or charging hub.
---
### Add a Charging Station (Charge Point)
To add a new charging station in CitrineOS Operator UI:
1. Navigate to:
```sql
Charging Stations → Add Charging Station
```
2. Select the previously created **Location**.
3. Use the default **Charge Point ID**:
```nginx
cp001
```
> ⚠️ **Important:**
> The Charge Point ID must exactly match the ID used by your physical charging station or simulator.
> By default, **EVerest** uses `cp001`.
4. Click **Save** to create the charging station.
---
### Add an EVSE (Electric Vehicle Supply Equipment)
Each Charging Station must contain at least one EVSE.
1. Open the Charging Station you just created.
2. Navigate to:
```sql
EVSEs → Add EVSE
```
3. Use a numeric **EVSE ID** (example: `1`).
4. Click **Save**.
> EVSEs represent physical charge points within a station (e.g., left/right ports).
---
### Add a Connector
Each EVSE must have at least one Connector.
1. Open the EVSE.
2. Navigate to:
```sql
Connectors → Add Connector
```
3. Choose:
- **Connector ID** (example: `1`)
- **Connector type** (Type2, CCS, etc.)
4. Click **Save**.
> Without a Connector, charging sessions cannot be started.
---
### Add an Authorization (ID Token)
To allow charging sessions, an ID Token must be authorized.
1. Navigate to:
```sql
Authorizations → Add Authorization
```
2. Create an **ID Token** (RFID, app token, etc.)
3. Mark it as **Accepted**
4. Click **Save**
> ⚠️ Note: This token will be used by EVerest to start charging.
> The EVerest simulator uses the **default RFID token** `DEADBEEF` **(ISO14443)**.
---
### Start the Charging Station (EVerest)
Now that the backend configuration is complete, start the EVerest simulator:
```bash
cd citrineos-core/apps/Server
pnpm run start-everest
```
This will:
**In Operator UI**
- Navigate to **Charging Stations**
- Status should show:
```nginx
Online
```
You can now track OCPP logs for the charging station.
## Usage
Once running, the Operator UI allows you to:
- View and manage charging stations and connectors
- Monitor transactions and sessions
- Trigger charging station commands (via CitrineOS Core)
- Query and manage data through Hasura-backed GraphQL APIs
## Hasura Authentication
There are two ways to authenticate to Hasura:
1. If you have \*_uncommented_ the Docker environment variable `HASURA_GRAPHQL_ADMIN_SECRET` in `citrineos-core`: 2. Ensure your `.env.local` has the `HASURA_ADMIN_SECRET` uncommented out 3. Please note that it is not recommended to use a Hasura Admin Secret in production.
2. If you have commented the Docker environment variable `HASURA_GRAPHQL_ADMIN_SECRET` in `citrineos-core`: 5. Ensure your `.env.local` has the `HASURA_ADMIN_SECRET` commented out, as the auth will be handled by your auth provider automatically.
## Related Documentation
- **CitrineOS Core**
- [Docs](https://github.com/citrineos/citrineos-core)
- **CitrineOS Project Docs**
- [Docs](https://citrineos.github.io)
- **Refine**
- [Docs](https://refine.dev/docs)
- **Hasura GraphQL Engine**
- [Docs](https://hasura.io/docs/2.0/index/)
- **Postgres Data Connector**
- [Docs](https://hasura.io/docs/2.0/databases/postgres/index/)
## Contributing
We welcome contributions from the community. If you would like to contribute to CitrineOS, please follow
our [contribution guidelines](https://github.com/citrineos/citrineos/blob/main/CONTRIBUTING.md).
## Licensing
CitrineOS and its subprojects are licensed under the Apache License, Version 2.0.
## Support and Contact
If you need help or want to report an issue:
- Open an issue on GitHub
- Reach out via the CitrineOS community channels