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
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ jobs:
make vet
make test

ui-test:
name: Frontend tests (vitest)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

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

- name: Run frontend unit tests
run: make test-ui

docker:
name: Build & Push Docker image (release only)
runs-on: ubuntu-latest
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ Dockerfile.local
Makefile.local
out/
static/
src/node_modules/
src/node_modules/
src/coverage/
/browser-ui
*.prof
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ VERSION ?= develop
EXTRA_TAGS ?=
PLATFORM ?= linux/amd64
CONTAINER_TOOL ?= docker
NPM ?= npm
UI_DIR ?= src

.PHONY: fmt vet tidy test docker-build docker-push deploy clean show-vars
.PHONY: fmt vet tidy test test-ui docker-build docker-push deploy clean show-vars

fmt:
go fmt ./...
Expand All @@ -21,7 +23,10 @@ tidy:
test:
go test -race -count=1 -cover ./...

docker-build: tidy fmt vet test
test-ui:
cd $(UI_DIR) && $(NPM) ci && $(NPM) run test

docker-build: tidy fmt vet test test-ui
$(CONTAINER_TOOL) buildx build \
--platform $(PLATFORM) \
--build-arg VERSION=$(VERSION) \
Expand Down Expand Up @@ -50,3 +55,5 @@ show-vars:
@echo "VERSION: $(VERSION)"
@echo "PLATFORM: $(PLATFORM)"
@echo "CONTAINER_TOOL: $(CONTAINER_TOOL)"
@echo "NPM: $(NPM)"
@echo "UI_DIR: $(UI_DIR)"
179 changes: 91 additions & 88 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,143 +1,146 @@
[![GitHub release](https://img.shields.io/github/v/release/alcounit/browser-ui)](https://github.com/alcounit/browser-ui/releases) [![Go Reference](https://pkg.go.dev/badge/github.com/alcounit/browser-ui.svg)](https://pkg.go.dev/github.com/alcounit/browser-ui) [![Docker Pulls](https://img.shields.io/docker/pulls/alcounit/browser-ui.svg)](https://hub.docker.com/r/alcounit/browser-ui)
# browser-ui

# Browser UI
**Web dashboard and live VNC viewer for the [Selenosis](https://github.com/alcounit/selenosis) ecosystem.**
A stateless Go server that serves a React frontend, lists live browser sessions, and proxies VNC to the browser pods — Kubernetes stays the source of truth.

Browser UI is a lightweight web application for the **Selenosis** ecosystem.
It provides a simple HTTP server that serves a static frontend and exposes a minimal backend API for browsing sessions and connecting to VNC.
[![GitHub release](https://img.shields.io/github/v/release/alcounit/browser-ui)](https://github.com/alcounit/browser-ui/releases)
[![Go Reference](https://pkg.go.dev/badge/github.com/alcounit/browser-ui.svg)](https://pkg.go.dev/github.com/alcounit/browser-ui)
[![Docker Pulls](https://img.shields.io/docker/pulls/alcounit/browser-ui.svg)](https://hub.docker.com/r/alcounit/browser-ui)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE)

<p align="center">
<a href="https://github.com/user-attachments/assets/b5fb55e2-2354-4dbb-8bab-25e7c78bc66c" target="_blank">
<img src="https://github.com/user-attachments/assets/b5fb55e2-2354-4dbb-8bab-25e7c78bc66c"
width="420"
alt="Browser UI dashboard" />
<img src="https://github.com/user-attachments/assets/b5fb55e2-2354-4dbb-8bab-25e7c78bc66c" width="420" alt="Browser UI dashboard" />
</a>

<a href="https://github.com/user-attachments/assets/95eae48b-d4a6-4638-a0fc-251cdd1d2ff0" target="_blank">
<img src="https://github.com/user-attachments/assets/95eae48b-d4a6-4638-a0fc-251cdd1d2ff0"
width="420"
alt="Browser UI VNC view" />
<img src="https://github.com/user-attachments/assets/95eae48b-d4a6-4638-a0fc-251cdd1d2ff0" width="420" alt="Browser UI VNC view" />
</a>
</p>

---

## Overview

- **Frontend**: static assets built with Node and served under `/ui/`
- **Backend**: Go HTTP server providing a small API and VNC WebSocket proxy
- **Event collector**: subscribes to `browser-service` events and maintains an in-memory session store

Browser UI is intentionally stateless: Kubernetes remains the source of truth and `browser-service` is the API boundary.
## How it fits

---

## Responsibilities
browser-ui is the dashboard of a small Kubernetes-native platform. It never talks to the cluster directly — it consumes **browser-service** and proxies VNC to the pods.

- Serve the web UI (static frontend)
- Provide a simple JSON API for listing and inspecting sessions
- Proxy VNC traffic from the UI to the underlying browser pod
- Track sessions in memory by consuming browser events
| Component | Role |
| --- | --- |
| **[selenosis](https://github.com/alcounit/selenosis)** | Stateless Selenium / Playwright / MCP hub. |
| **[seleniferous](https://github.com/alcounit/seleniferous)** | Sidecar proxy inside each browser pod (incl. the VNC endpoint). |
| **[browser-controller](https://github.com/alcounit/browser-controller)** | Operator reconciling `Browser` / `BrowserConfig` CRDs into pods. |
| **[browser-service](https://github.com/alcounit/browser-service)** | REST + SSE facade over the CRDs. **browser-ui talks only to this.** |
| **browser-ui** (this repo) | Web dashboard, live session list, in-browser VNC viewer. |
| **[selenosis-deploy](https://github.com/alcounit/selenosis-deploy)** | Helm chart that deploys the whole stack. |

---

## Dependency on browser-service

Browser UI **depends on browser-service** for:
## How it works

- REST access to `Browser` resources
- Event stream (ADDED / MODIFIED / DELETED) used to populate the UI session store
- **Frontend** — React/TypeScript (noVNC, TanStack Query), built with Vite and served under `/ui/`.
- **Backend** — Go HTTP server (chi/v5, zerolog) exposing a small JSON API and a VNC WebSocket proxy.
- **Event collector** — subscribes to the `browser-service` SSE stream (ADDED / MODIFIED / DELETED) and keeps an **in-memory** session store derived from `Browser` resources.

The UI assumes that:
- `browser-service` is reachable at `BROWSER_SERVICE_URL`
- `browser-controller` and CRDs are already installed in the cluster (indirect dependency via browser-service)
browser-ui is stateless: restart it freely, run multiple replicas. It depends on `browser-service` being reachable at `BROWSER_SERVICE_URL` (and, indirectly, on the controller and CRDs being installed).

---

## HTTP Endpoints
## VNC viewer

### UI
The viewer (`GET /api/v1/browsers/{id}/vnc`) is a WebSocket proxy to the session pod's seleniferous VNC endpoint. The browser image's VNC server is password-protected, and vendors set that password differently (some bake it into the image), so there is **no global server-side password** — the user supplies it in the UI and it is resolved on the client from what was saved before:

- `GET /` → redirects to `/ui/`
- `GET /ui/` → UI entrypoint (`index.html`)
- `GET /ui/*` → static assets

### API

Base path:

```
/api/v1
```
1. password saved for this browser **name + version** (`localStorage`, e.g. `chrome@146.0`),
2. password saved for this browser **name** (`localStorage`, any version).

Endpoints:
**On first use (nothing saved) or when the password doesn't match**, the viewer shows an inline prompt so the user types the password. After a successful connect it offers to remember it:

- `GET /api/v1/status/`
Returns active sessions and supported browsers from the in-memory store
- **For all browsers of this name** — reused for every version of that browser (e.g. all `chrome`).
- **Only this browser version** — reused only for that exact `name + version` (e.g. `chrome 146.0`).
- **Don't save** — used once, nothing stored.

- `POST /api/v1/browsers/`
Create and start a browser session. Body: `{"browserName":"chrome","browserVersion":"146.0","selenosisOptions":{}}`
Both scopes persist in `localStorage` (the session id is random and dies with the pod, so it is never used as a key). The version-scoped password takes priority over the name-scoped one.

- `GET /api/v1/browsers/{browserId}/`
Get a single session by Browser ID
Wrong passwords surface a clear `securityfailure` message and re-prompt; a hard attempt cap prevents retry loops. This keeps a single deployment usable across mixed browser vendors without forcing one shared VNC password.

- `DELETE /api/v1/browsers/{browserId}/`
Delete a manually-started session (only sessions created via `POST /browsers/`)
---

- `GET /api/v1/browsers/{browserId}/vnc`
WebSocket proxy to the browser pod VNC endpoint
## Configuration

- `GET /api/v1/browsers/{browserId}/vnc/settings`
Returns VNC settings (currently returns the password)
Configured via environment variables (read in `cmd/browser-ui/main.go`):

### Health
| Variable | Default | Description |
| --- | --- | --- |
| `LISTEN_ADDR` | `:8080` | HTTP listen address. |
| `BROWSER_SERVICE_URL` | `http://browser-service:8080` | `browser-service` base URL. |
| `BROWSER_NAMESPACE` | `default` | Namespace for session subscriptions. |
| `BROWSER_STARTUP_TIMEOUT` | `3m` | Max wait for a manually started browser to become ready. |
| `UI_STATIC_PATH` | `/app/static` | Path to the built frontend assets. |
| `BASIC_AUTH_FILE` | | Path to a JSON users file; when set, the UI requires login. |

- `GET /health` → returns `{"status":"ok"}`
Basic Auth is optional. When `BASIC_AUTH_FILE` is set, the UI gates the API behind a login (`/auth/login` issues an HttpOnly cookie) and the file is watched for hot reload.

---

## VNC Connectivity
## Endpoints

Browser UI exposes a WebSocket endpoint (`/vnc`) that proxies traffic to the browser pod VNC WebSocket:
<details>
<summary><b>UI, API, auth, and health routes</b></summary>

- Backend target (resolved from session data):
- `ws://<browserPodIP>:4445/selenosis/v1/vnc/<sessionId>`
**UI**
- `GET /` → redirects to `/ui/`
- `GET /ui/`, `GET /ui/*` → frontend entrypoint and static assets

The UI also exposes `/vnc/settings` for clients that need the VNC password.
**Auth**
- `GET /api/v1/auth/config` → whether auth is enabled
- `POST /api/v1/auth/login` / `POST /api/v1/auth/logout`

---
**Sessions** (under `/api/v1`, auth-gated when enabled)
- `GET /status/` → active sessions + supported browsers from the in-memory store
- `POST /browsers/` → create/start a session — body `{"browserName":"chrome","browserVersion":"146.0","selenosisOptions":{}}`
- `GET /browsers/{browserId}/` → single session
- `DELETE /browsers/{browserId}/` → delete a manually started session
- `GET /browsers/{browserId}/vnc` → VNC WebSocket proxy to the pod

## Configuration
**Health**
- `GET /health` → `{"status":"ok"}`

Browser UI is configured using environment variables:

- `LISTEN_ADDR` — address to listen on (default `:8080`)
- `BROWSER_SERVICE_URL` — browser-service base URL (default `http://browser-service:8080`)
- `BROWSER_NAMESPACE` — namespace used for subscriptions (default `default`)
- `VNC_PASSWORD` — VNC password exposed via `/vnc/settings` (default `secret`)
- `UI_STATIC_PATH` — path to static UI assets (default `/app/static`)
</details>

---

## Build and image workflow
## Build

The project builds and packages entirely via Docker (multi-stage: Node for the frontend, Go for the backend) — a local Go/Node install is not required for the final image.

The project is built and packaged entirely via Docker. Local Go installation is not required for producing the final artifact.
```bash
make test # go tests
make test-ui # frontend unit tests (vitest)
make docker-build
```

## Build variables
<details>
<summary><b>Makefile variables</b></summary>

The build process is controlled via the following Makefile variables:
| Variable | Description |
| --- | --- |
| `BINARY_NAME` | Produced binary name (fixed: `browser-ui`). |
| `REGISTRY` | Docker registry prefix (default `localhost:5000`). |
| `IMAGE_NAME` | Full image name, `$(REGISTRY)/$(BINARY_NAME)`. |
| `VERSION` | Image tag (default `develop`). |
| `EXTRA_TAGS` | Additional `-t` tags for `docker-push`. |
| `PLATFORM` | Target platform (default `linux/amd64`). |
| `CONTAINER_TOOL` | Container build tool (default `docker`). |
| `NPM` / `UI_DIR` | npm binary and frontend directory for `test-ui` (defaults `npm` / `src`). |

Variable Description
- BINARY_NAME Name of the produced binary (browser-ui).
- REGISTRY Docker registry prefix (default: localhost:5000).
- IMAGE_NAME Full image name (<registry>/browser-ui).
- VERSION Image version/tag (default: develop).
- PLATFORM Target platform (default: linux/amd64).
- CONTAINER_TOOL docker cmd
`REGISTRY` and `VERSION` are expected to be supplied externally so the same Makefile works locally and in CI.

REGISTRY, VERSION is expected to be provided externally, which allows the same Makefile to be used locally and in CI.
</details>

---

## Deployment

Helm chart [selenosis-deploy](https://github.com/alcounit/selenosis-deploy)
Deployed as part of the full stack via the [selenosis-deploy](https://github.com/alcounit/selenosis-deploy) Helm chart.

## License

[Apache-2.0](./LICENSE)
Loading
Loading