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
9 changes: 7 additions & 2 deletions .github/AGENTS.md → .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# robotframework-dashboard — Agent Guide
# robotframework-dashboard — Copilot Instructions

This file gives AI agents and contributors the context needed to work effectively in this codebase.

## How to Use the Skills Files

Before starting work on any non-trivial task, read the relevant skill file from `.github/skills/`. Each file contains deep domain knowledge that avoids re-exploring the codebase from scratch. Use the table in the **Skills** section below to pick the right one(s). Read the skill file with a file-read tool before making any changes.

---

## Project Purpose
Expand Down Expand Up @@ -109,7 +113,8 @@ The `.github/skills/` directory contains domain-specific knowledge files:
| `unit-tests.md` | Python unit tests (pytest, coverage, test layout, fixtures) |
| `js-unit-tests.md` | JavaScript unit tests (Vitest, mocking patterns, which modules are testable) |
| `server-api.md` | All REST endpoints, authentication, log linking, auto-update behavior |
| `filtering-and-settings.md` | Filter pipeline, settings object, localStorage persistence, layout/GridStack system |
| `filtering-and-settings.md` | Filter pipeline, settings object, localStorage persistence, layout/GridStack system, **filter profiles** (data structure, all profile functions, merge modal) |
| `documentation.md` | All documentation locations (docs/, README.md, CONTRIBUTING.md, setup.py), page map, and checklist for keeping docs in sync when features change |

---

Expand Down
92 changes: 92 additions & 0 deletions .github/skills/documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
description: Use when adding, updating, or reviewing documentation — new features, CLI options, filters, settings, graphs, server, or any user-facing behaviour change. Covers docs/, README.md, CONTRIBUTING.md, and setup.py long_description.
---

# Documentation

## Files to Check on Every Documentation Change

Whenever a user-facing feature is added or changed, check **all four** of these locations and update each one as needed:

| File | What it contains |
|---|---|
| `docs/<page>.md` | Full VitePress documentation site — the primary place for detailed docs |
| `README.md` | PyPI / GitHub landing page: overview, key features list, quick-start examples, quick-links table |
| `setup.py` → `long_description` | PyPI package description (plain markdown embedded in `setup()`); mirrors the README overview and getting-started sections |
| `CONTRIBUTING.md` | Developer guide: how to run locally, test levels, Docker usage — update when build/test commands or workflow changes |

> `setup.py`'s `long_description` is a copy of the README's Overview and Getting Started sections. Keep them in sync when those sections change.

---

## docs/ — VitePress Site

Served at `https://marketsquare.github.io/robotframework-dashboard/`. Source is in `docs/`. Built with VitePress (`package.json` at root — this is the **only** purpose of `package.json`). The `docs/index.md` is the home page (hero + feature cards).

### Page Map

| File | Title | Contents |
|---|---|---|
| `docs/index.md` | Home | Hero section, feature cards, links to all pages |
| `docs/getting-started.md` | Getting Started | Installation steps, first run, verifying output |
| `docs/installation-version-info.md` | Installation & Version Info | pip install variants (base / server / all), Python & RF requirements, version changelog |
| `docs/basic-command-line-interface-cli.md` | Basic CLI | All CLI flags (`-o`, `-d`, `-n`, `-r`, `--server`, etc.), flag descriptions, basic usage examples |
| `docs/advanced-cli-examples.md` | Advanced CLI & Examples | Combined commands, project tagging, aliases, batch imports, message config, performance tips |
| `docs/tabs-pages.md` | Tabs / Pages | Overview, Dashboard, Compare, and sub-pages (Run/Suite/Test/Keyword); what each tab shows |
| `docs/graphs-tables.md` | Graphs & Tables | Chart types per section, DataTables, how to read each graph |
| `docs/filtering.md` | Filtering | All global filter types (Runs, Run Tags, Versions, From/To Date+Time, Amount, Metadata), Filter Profiles (create, apply, update, delete, **merge**), section-specific filters |
| `docs/customization.md` | Customization | Layout editor (GridStack), section show/hide, graph show/hide, drag-and-drop ordering |
| `docs/settings.md` | Settings | Settings modal options: themes, aliases, display toggles, JSON config (`--jsonconfig` / `--forcejsonconfig`) |
| `docs/dashboard-server.md` | Dashboard Server | `--server` flag, REST API overview, authentication, auto-update, log linking via server |
| `docs/log-linking.md` | Log Linking | How to configure log paths so the dashboard links back to Robot Framework HTML logs |
| `docs/listener-integration.md` | Listener Integration | Using `robotdashboardlistener.py` to auto-update the dashboard after every test run |
| `docs/custom-database-class.md` | Custom Database Class | Implementing `AbstractDatabaseProcessor`, `--databaseclass` flag, example MySQL class |
| `docs/contributions.md` | Contributions | How to contribute, issue/PR guidelines |

### docs/index.md — Feature Cards and Quick Links

`docs/index.md` has two sections that need updating when features are added:
1. **Hero feature cards** (`features:` YAML) — one card per major topic
2. `README.md` has a matching **Quick Links** section — keep both in sync

### VitePress Conventions

- All pages start with YAML frontmatter (`---`).
- Pages that have deep headings use `outline: deep` in frontmatter.
- Internal links use relative `.md` paths (e.g. `[Advanced CLI](advanced-cli-examples.md)`).
- Images live in `docs/public/`.

---

## README.md

Sections to keep up to date:

| Section | Update when |
|---|---|
| **Key Features** (emoji bullet list) | A significant user-facing feature is added or removed |
| **Quick Links** table | A new docs page is added, or a page is renamed/removed |
| **Basic Usage** examples | Default CLI behaviour changes |

---

## CONTRIBUTING.md

Update when:
- Test commands or script names change (`scripts/*.sh` / `scripts/*.bat`)
- A new test level is added
- Docker / CI workflow changes
- New development dependencies are introduced

---

## Typical Update Checklist

When adding a new user-facing feature:

- [ ] Add or update the relevant `docs/<page>.md` section
- [ ] If it is a major feature, add a bullet to **Key Features** in `README.md`
- [ ] Add or update the matching Quick Link in `README.md` if a new page was created
- [ ] If a new docs page was added, add a feature card to `docs/index.md`
- [ ] If the Overview/Getting Started sections are affected, mirror changes into `setup.py` `long_description`
- [ ] If build/test workflow changed, update `CONTRIBUTING.md`
64 changes: 59 additions & 5 deletions .github/skills/filtering-and-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,65 @@ After filtering: updates count headlines (`#runTitle`, etc.), repopulates all dr

## Filter Profiles

Named snapshots of the filter modal state, stored in `settings.filterProfiles` (localStorage-only).

- `build_profile_from_checks()` — captures all checkbox/input/select states from the filter modal
- `apply_filter_profile(name)` — restores a saved profile into the modal's UI and triggers filtering
- The active profile name and whether it has been modified since last save is shown in the profile dropdown
Named snapshots of the filter modal state, stored in `settings.filterProfiles` (localStorage-only key, never dropped by `merge_deep`).

### Data structure
```js
settings.filterProfiles = {
"ProfileName": {
runs: "All", // string — run name or "All"
runTags: [{ id: "All", checked: true }, ...], // array of {id, checked}
useOrTags: false, // boolean — AND/OR tag logic
projectVersions: [{ value: "All", checked: true }, ...],
fromDate: "YYYY-MM-DD", fromTime: "HH:MM",
toDate: "YYYY-MM-DD", toTime: "HH:MM",
metadata: "All",
amount: "10" // string (input value)
}
}
```
A profile need not contain all keys — only the keys that were checked when the profile was saved.

### Key functions (`filter.js`)
| Function | What it does |
|---|---|
| `capture_current_filters()` | Reads all filter DOM controls → plain profile object |
| `build_profile_from_checks()` | Like `capture_current_filters()` but only for keys whose `.filter-profile-check` checkbox is checked |
| `save_filter_profile_to_storage(name, data)` | Merges into `settings.filterProfiles`, persists immediately |
| `load_filter_profiles()` | Returns `settings.filterProfiles \|\| {}` |
| `delete_filter_profile(name)` | Removes one entry and persists |
| `apply_filter_profile(profile, name)` | Writes each key of a profile back into the filter modal DOM |
| `populate_filter_profile_select()` | Rebuilds `#filterProfileList` `<ul>` from stored profiles |
| `enter_profile_edit_mode()` / `exit_profile_edit_mode()` | Shows/hides per-row `.filter-profile-check` checkboxes + `#filterProfileEditorInline` |
| `update_profile_select_display()` | Updates `#profileModifiedDot` and `#updateFilterProfile` visibility |
| `update_active_profile()` | Re-saves only the keys that were in the original profile |
| `merge_two_profiles(a, b)` | Merges two partial profiles with widest-horizon rules (see below) |
| `capture_default_filters()` | Snapshots the initial filter state so checkboxes can show which fields differ |

### Profile UI layout (dashboard.html)
- **`.filter-profile-bar`** stripe between modal header and body: holds `#filterProfileEditorInline` (name input + Save Profile) on the left, `#updateFilterProfile` + `#selectFilterProfile` fake-dropdown on the right.
- **`.filter-profile-check`** checkboxes appear inline on each filter row (hidden outside edit mode).
- **`#filterProfileList`** `<ul>` inside `#filterProfileCheckBoxes` — each `<li>` has a `.filter-profile-apply` span and a `.filter-profile-delete` ×.

### Merge profiles modal (`#mergeProfilesModal`)
Opened by `#mergeFilterProfiles` button. Layout mirrors the filter modal:
1. Modal header + Close button
2. `.filter-profile-bar` stripe: name input + "Add Merged Profile" button
3. Modal body: two-column profile selects with per-field checkboxes + "Resulting Filters" preview `<dl>`

When a profile is selected, `render_merge_profile_settings(profile, side)` builds a checkbox list (one row per defined field) in `#mergeLeftSettings` / `#mergeRightSettings`. Any checkbox change calls `update_merge_result_preview()`, which runs `get_checked_partial_profile()` on both sides then `merge_two_profiles()` and renders the result as a `<dl>`.

### `merge_two_profiles` rules
| Field | Rule |
|---|---|
| `runs` / `metadata` | Same value → keep; different → `"All"` |
| `runTags` / `projectVersions` | Union of checked entries (OR) |
| `useOrTags` | OR wins |
| `fromDate` + `fromTime` | Earlier datetime (widest start) |
| `toDate` + `toTime` | Later datetime (widest end) |
| `amount` | `Math.max` of both values |

Fields present in only one side pass through unchanged.

---

Expand Down
52 changes: 51 additions & 1 deletion docs/dashboard-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ RobotFramework Dashboard includes a built-in server that lets you host the dashb

## Why Use the Dashboard Server

- Host a centralized, always-available dashboard accessible via HTTP
- Host a centralized, always-available dashboard accessible via HTTP or HTTPS
- Enable remote clients to push or delete runs in your database
- Provide a web-based admin interface for manual management
- Secure access via optional basic authentication (username/password)
- Secure transport via optional HTTPS using your own SSL certificate
- The server automatically uses offline CDN (js/css) because `FastAPI-offline` is used, making it compatible with [`--offlinedependencies`](https://marketsquare.github.io/robotframework-dashboard/advanced-cli-examples) for full offline usage!
> **Tip:** To implement your server into your test runs look at the example [listener](/listener-integration.md) integration!

Expand Down Expand Up @@ -48,6 +49,22 @@ Or with a custom host and port plus authentication:
robotdashboard -s host:port:user:password
```

**Enable HTTPS**

To serve over HTTPS, provide paths to your SSL certificate and private key:

```bash
robotdashboard --server --ssl-certfile cert.pem --ssl-keyfile key.pem
```

Combined with a custom host, port, and authentication:

```bash
robotdashboard -s 0.0.0.0:8543:admin:secret --ssl-certfile cert.pem --ssl-keyfile key.pem
```

> **Tip:** Both `--ssl-certfile` and `--ssl-keyfile` must be provided together. Use any valid PEM certificate/key pair, including self-signed certificates for testing.

Once the server is running, open your browser at the configured address (for example, `http://127.0.0.1:8543/`) to access:

- The **admin page** (for manual control), this page lives on `/admin`: `http://127.0.0.1:8543/admin`
Expand Down Expand Up @@ -88,6 +105,21 @@ If you start the server with a username and password, the admin page will be pro

The dashboard itself (the HTML) does **not** require authentication. API calls as of now do also **not** require authentication.

## Security: HTTPS (Optional)

To encrypt traffic between clients and the server, you can enable HTTPS by providing an SSL certificate and private key:

```bash
robotdashboard --server --ssl-certfile cert.pem --ssl-keyfile key.pem
```

- Both `--ssl-certfile` and `--ssl-keyfile` must be provided together.
- Any valid PEM certificate/key pair is accepted, including self-signed certificates.
- When HTTPS is enabled, the server URL becomes `https://host:port/` instead of `http://host:port/`.
- The [listener](/listener-integration.md) must be configured with `protocol=https` to match.

> **Tip:** For production deployments it is recommended to use a certificate from a trusted Certificate Authority (CA). For local or internal testing, a self-signed certificate is sufficient.

## Working Programmatically with the Server

You can interact with the server programmatically using HTTP, Python, or Robot Framework. There are example scripts in the `example/server` folder:
Expand Down Expand Up @@ -192,3 +224,21 @@ Use `--noautoupdate` when:
- You are uploading many outputs in quick succession and want uploads to return fast.
- You prefer to control exactly when the dashboard is updated.
:::

## Known Issues

### `ConnectionResetError` on Windows with HTTPS

When running the server with HTTPS on Windows, you may see the following error in the server console:

```
Exception in callback _ProactorBasePipeTransport._call_connection_lost()
...
ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host
```

This is a known Windows-specific behaviour caused by the interaction between uvicorn's `ProactorEventLoop` and TLS connections. Browsers and HTTP clients sometimes open speculative TCP connections that are dropped before the TLS handshake completes. On Linux this is silently handled, but on Windows the asyncio `ProactorEventLoop` surfaces it as an unhandled exception.

It does **not** affect any completed request — all requests that returned `200 OK` were served and encrypted correctly. The server continues to function normally.

The server logs will still show `HTTP/1.1` in request lines (e.g. `GET / HTTP/1.1`) even when HTTPS is active. This is expected: uvicorn decrypts TLS traffic first and then processes it as a regular HTTP/1.1 request internally. The traffic is encrypted in transit regardless of what the log line shows.
25 changes: 25 additions & 0 deletions docs/filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,31 @@ Filter Profiles let you save, name, and reapply a combination of filter settings
- In the profile list, click the **×** next to a profile name.
- A confirmation prompt prevents accidental deletion.

#### Merging Profiles

The **Merge Profiles** button (in the Filters modal header) opens a dedicated modal for combining filter settings from two saved profiles into a new one.

**Layout of the merge modal:**
- A **Profile Name** field and **Add Merged Profile** button sit directly below the title bar.
- Below that, two columns let you independently choose a **Left Profile** and a **Right Profile**.
- Once a profile is selected in a column, its individual filter settings appear as a checklist. Each row is pre-checked; uncheck any row to exclude that setting from the merge.
- A **Resulting Filters** section at the bottom shows a live preview of what the merged profile will contain, updating instantly as you change selections or toggle checkboxes.

**Merge rules** — when the same filter field is checked in both columns, the values are combined as follows:

| Filter | Rule |
|---|---|
| **Run Tags** / **Versions** | Union of all checked entries (OR) |
| **Use OR Tags** | OR wins (more permissive) |
| **From Date / Time** | The earlier value is kept (widest horizon) |
| **To Date / Time** | The later value is kept (widest horizon) |
| **Amount** | The larger value is kept |
| **Runs** / **Metadata** | Kept if both sides have the same value; otherwise resets to **All** |

Fields checked on only one side pass through unchanged.

You can also use this modal with only one column selected to quickly create a copy of an existing profile under a new name, with any settings individually removed.

### Section Filters on Dashboard

The Dashboard is divided into four sections: **Run, Suite, Test, Keyword**. Each section has specific filtering options that apply only to that section.
Expand Down
22 changes: 21 additions & 1 deletion docs/listener-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,24 @@ robot --listener robotdashboardlistener.py:uploadlog=true tests.robot
robot --listener path/to/listeners/robotdashboardlistener.py:host=10.0.0.5:port=8543 tests.robot
```

**With HTTPS**

```bash
robot --listener robotdashboardlistener.py:protocol=https:port=8543 tests.robot
```

**With HTTPS and SSL verification disabled (for self-signed certificates)**

```bash
robot --listener robotdashboardlistener.py:protocol=https:sslverify=false tests.robot
```

**With HTTPS and a custom CA bundle**

```bash
robot --listener robotdashboardlistener.py:protocol=https:sslverify=/path/to/ca-bundle.pem tests.robot
```

## Full Listener Options

The listener supports the following arguments:
Expand All @@ -71,13 +89,15 @@ The listener supports the following arguments:
| `uploadlog` | Set to `true` to upload the log file to the server (default: `false`) |
| `host` | Dashboard server hostname (default: `127.0.0.1`) |
| `port` | Dashboard server port (default: `8543`) |
| `protocol` | Protocol to use when connecting to the server: `http` or `https` (default: `http`) |
| `sslverify` | SSL certificate verification for HTTPS: `true` (default), `false` (skip verification for self-signed certs), or a path to a CA bundle file |
| `limit` | Maximum number of runs stored in the database (older runs will be auto-deleted, based on the order in the database) |
| `output` | Required only when using Pabot **with a custom output.xml name** |

**Example with all options**

```bash
robot --listener robotdashboardlistener.py:tags=dev1,dev2:version=v2.0:host=127.0.0.2:port=8888:limit=100:uploadlog=true tests.robot
robot --listener robotdashboardlistener.py:tags=dev1,dev2:version=v2.0:host=127.0.0.2:port=8888:protocol=https:sslverify=false:limit=100:uploadlog=true tests.robot
```

## Using the Listener with Pabot
Expand Down
8 changes: 8 additions & 0 deletions example/listener/robot.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@
# [listeners]
# "robotdashboardlistener.py" = ["host=127.0.0.2", "port=8888", "limit=100"]

# HTTPS with SSL verification disabled (for self-signed certificates)
# [listeners]
# "robotdashboardlistener.py" = ["protocol=https", "port=8543", "sslverify=false"]

# HTTPS with a custom CA bundle
# [listeners]
# "robotdashboardlistener.py" = ["protocol=https", "port=8543", "sslverify=/path/to/ca-bundle.pem"]

# All features combined
# [listeners]
# "robotdashboardlistener.py" = ["tags=dev1,dev2", "version=v2.0", "uploadlog=true", "limit=100", "host=127.0.0.2", "port=8888"]
Loading
Loading