Event Viewer - sebiboga/testlink-upgraded GitHub Wiki
The Event Viewer is a redeveloped, modern screen for real-time monitoring of all system events in TestLink. It replaces the legacy event viewer with a clean HTML+JavaScript interface powered by its own BFF (Backend-for-Frontend) API.

Path: System > Event Viewer
URL: gui/templates/eventviewer/eventviewer.html
Prerequisite: You must be logged in with the mgt_view_events right (all users by default).
- Screen Layout
- Filters
- Charts
- Events Table
- Row Detail View
- Delete Events
- Rights & Access Control
- API Endpoints
- Color Scheme
The page is divided into four horizontal sections:
| Section | Description |
|---|---|
| Header | Teal banner with title "Event Viewer" and subtitle "real-time log monitoring" |
| Filters bar | Dark charcoal bar with filter controls |
| Charts row | Two side-by-side chart panels |
| Events table | DataTables-powered table with expandable rows |
| Footer | Total event count and generation timestamp |
No iframes, no PHP rendering, no Smarty templates β pure static HTML with JavaScript calling the BFF API.
The filter bar sits at the top and allows narrowing down the displayed events. All filters are applied server-side via query string parameters.
| Filter | Type | Description |
|---|---|---|
| Log Levels | Multi-select (default: all selected) | Filter by event severity. Hold Ctrl/Cmd to select multiple. Available levels: AUDIT, ERROR, WARNING, INFO, DEBUG, L18N |
| User(s) | Multi-select (multiple size="4") |
Filter events by one or more users at once β Ctrl/Cmd+click to select several. Matches legacy testers[] (size=5) multi-select. Selected users stay selected after Apply. Empty selection = events from all users |
| From | Date picker (dd/mm/yyyy) | Start date of the date range filter |
| To | Date picker (dd/mm/yyyy) | End date of the date range filter |
| Apply | Button | Reloads the table, charts, and footer with the current filter settings |
| Clear Events | Button (admin only) | Deletes events matching the current filters (see Delete Events) |
Note: The date pickers use the daterangepicker library but are configured to accept a single date per field (From and To), not a range.

Two charts provide a visual overview of event distribution:
- Shows the count and percentage of events for each log level
- Color-coded to match the level badges in the table
- Includes an inline legend on the right side of the chart
- Uses Chart.js v1
Doughnutwith 45% inner cutout
- Shows the number of events per day over the last 30 days
- Area-filled line chart with the TestLink teal accent color
- Automatically generated from the filtered dataset
- Uses Chart.js v1
Linewith bezier curves -
Hover tooltip: moving the pointer over a plotted point shows a dark tooltip with the date and event count (e.g.
2026-09-04: 2), matching the doughnut chart's tooltip. Implemented ingui/templates/dashio/lib/tl-line-tooltip.jsbecause the bundled Chart.js v1 build has no native tooltip support.

Both charts update whenever the Apply button is clicked with new filter settings.
A DataTables-powered table listing all matching events. By default the rows are grouped by Log Level into collapsible groups (data ordered by level, then by timestamp descending β newest first within each group), mirroring the legacy ExtTable grouping (eventviewer.legacy.php:263-274, setGroupByColumnName(th_loglevel) + sortDirection=DESC).
A toolbar sits directly above the table with three actions plus a status badge:
| Button | Icon | Description |
|---|---|---|
| Expand all groups | plus-square | Expands every collapsed Log Level group |
| Collapse all groups | minus-square | Collapses every Log Level group to its header only |
| Show all columns | columns | Toggles the hidden Transaction column (see below) |
| badge | β | "Grouped by log level" status indicator |
Clicking an individual group header row toggles that group's expand/collapse state. Level groups show their element count, e.g. ERROR (2 items).

| Column | Width | Description |
|---|---|---|
| (expand) | 30px | Clickable chevron icon to toggle the detail row |
| Timestamp | auto | Formatted as dd/mm/yyyy hh:mm:ss
|
| Level | auto | Colored badge showing the log level (AUDIT, ERROR, WARNING, INFO, DEBUG, L18N) β the grouping key |
| User | auto | Display name of the user who triggered the event, or - for system events |
| Description | auto | The event description text. Clicking the row also expands the detail view |
| Transaction | auto |
Hidden by default. Revealed via the "Show all columns" toolbar button; shows the per-row transaction id (e.g. #1234), mirroring legacy th_transaction
|
- Pagination: 25 rows per page by default
-
Grouping: Rows are grouped by Log Level (uses the DataTables RowGroup extension,
rowGroup.dataSrc). Within each group rows sort by timestamp descending. - Group collapse: Click a group header to collapse/expand that level; use the toolbar to expand or collapse all at once.
-
Sorting: Click any column header (except expand and description) to sort ascending/descending. The Timestamp column sorts chronologically by the numeric epoch, not by the
dd/mm/yyyydisplay string β the column cell carries[epoch, formatted]and an orthogonalrender(data,type)returns the epoch for thesorttype (legacy hidden-<!--epoch-->-comment parity,eventviewer.legacy.php:241-243). This keeps month/year boundaries correct (e.g. a 31/07/2025 event sorts before a 04/08/2026 one in ascending order) in both directions. - Search: Use the DataTables search box to filter the visible rows client-side
- Row expand: Click the chevron icon or the description text to expand/collapse the detail row
Ascending sort by Timestamp β chronological across month/year boundaries (31/07/2025 before 06/06/2026 before 04/08/2026 β¦), verified with seeded events from the legacy gap report (Refs #874):

(Before the fix the same click sorted by the dd/mm/yyyy string β 04/08/2026, 04/09/2026, 06/06/2026, 09/09/2026, 31/07/2025 β see issue-874-string-sort-before.png.)
When the current filter set matches zero events (e.g. a date range entirely in the future), the page reproduces the legacy behavior (lib/events/eventviewer.legacy.php:72-74 β eventviewer.tpl:184-198): the events table and its toolbar are hidden and a localized "No events found." feedback box is shown in their place (#noEventsBox, white card with a teal left border and info icon). The message is routed through TLi18n.t('ev.noEvents'), so it renders per-locale β e.g. "Nu au fost gΔsite evenimente." in Romanian, "Keine Ereignisse gefunden." in German.
The DataTables engine strings are also localized for the same condition:
-
emptyTable(server-side empty result in the hidden grid), -
infoEmpty(the info/footer line), -
zeroRecords(a client-side search box query that matches nothing).
All three point at ev.noEvents, present in all 10 locale bundles.


Expanding a row reveals the complete event record (legacy eventinfo.tpl parity) in a bordered detail panel:
| Field | Description |
|---|---|
| Level | Log level (AUDIT, ERROR, WARNING, INFO, DEBUG, L18N) |
| Timestamp | Full timestamp of the event |
| Source | The system component that generated the event |
| Description | The event description text |
| Session information (only when the event has a transaction) | Section header |
| β User | Display name of the user who triggered the event (falls back to the raw user id) |
| β Session ID | The real PHP session id from transactions.session_id (e.g. qun27mjdmgqeerpm1ds5gn0lfk) |
| β Transaction | The transaction id (shown with # prefix) β distinctly labelled so it is not mistaken for the PHP session |
Activity (only when objectID is set)
|
Section header |
| β Activity code | The activity code describing what happened |
| β Object ID | The database ID of the object |
| β Object Type | The type of object involved (e.g., test case, test plan) |
The detail data is fetched on-demand via GET /api/eventviewer/index.php/events/{id}.

Users with the events_mgt right (typically admins) see a Clear Events button in the filter bar.
- Clicking the button shows a confirmation dialog: "Delete events matching current filters?"
- Events are deleted based on the currently selected log level filter
- If no log levels are selected, all events are deleted (use with caution)
- After deletion, the table and charts reload automatically
-
All filters are reset after a successful delete (legacy BUGID 3908 parity,
lib/events/eventviewer.legacy.php:44-50): the Log Levels, User(s), From and To controls are cleared so the full event log is re-shown with no stale filter. The Date controls are reset in JS as well. Only the per-object drill-down filter (when opened via "Show event history") is preserved, since it is a page-level context rather than a user-adjustable filter. - The button is hidden for users without the
events_mgtright
Audit trail (legacy parity): every deletion is itself audited β the BFF calls logAuditEvent() right after deleteEventsFor(), so the Event Viewer history stays traceable:
- No log level selected β
TLS("audit_all_events_deleted", login)β "User 'admin' deleted all events" - Specific levels selected β
TLS("audit_events_with_level_deleted", login, levelNames)β "User 'admin' deleted events with level: Error,Warning,Info,Debug,Audit,L18N"
Both rows are written with log level AUDIT, source events, and activity code DELETE. This mirrors lib/events/eventviewer.legacy.php:27-43.


In legacy TestLink 1.9.20, lib/events/eventviewer.php was gated by testlinkInitPage(...checkRights) β a user without the mgt_view_events right received an access-denied screen, and even direct URL access was blocked. lib/events/eventinfo.php (row detail) required the same right; only the clear action (events deletion) could be reached via events_mgt alone. The Events menu item in the top bar is rendered only for users holding events_mgt OR mgt_view_events (cfg/const.inc.php:908).
The modern screen reproduces this exactly:
| Right | Effect |
|---|---|
mgt_view_events |
Required to view the event log (all read endpoints). Without it, every GET /api/eventviewer/... route returns HTTP 403 {"status":"error","message":"Forbidden: mgt_view_events right required"}, and the page itself renders the access-denied panel (sees no filters, charts or table). |
events_mgt |
Required for the Clear Events deletion action (DELETE /api/eventviewer/index.php/events). Without it the button stays hidden and the DELETE route returns 403. |
The access-denied panel is an i18n block (ev.accessDeniedTitle / ev.accessDeniedMsg) shown by the UI as soon as the /events/meta/rights probe is rejected (or fails) β even a user who deep-links directly to eventviewer.html without the right gets the panel and no data calls fire.


The Event Viewer uses a dedicated BFF API at /api/eventviewer/index.php. All endpoints require an authenticated session, and all read (GET/HEAD/OPTIONS) routes additionally require the mgt_view_events right β they return 403 Forbidden when it is missing. The only route gated by a different right is DELETE /events, which requires events_mgt.
| Method | Endpoint | Description |
|---|---|---|
| GET | /events/meta/logLevels |
Returns available log level codes and names |
| GET | /events/meta/users |
Returns all users (id, login, displayName) for the user filter dropdown |
| GET | /events/meta/rights |
Returns the current user's canDelete and canView permissions |
| Method | Endpoint | Description |
|---|---|---|
| GET | /events |
List events with optional filters (see below) |
| GET | /events/{id} |
Get full details of a single event |
| Parameter | Type | Description |
|---|---|---|
logLevel |
Comma-separated ints | Filter by log level codes (e.g., logLevel=1,2) |
user |
Comma-separated ints | Filter by user IDs |
startDate |
String (dd/mm/yyyy) | Events from this date onwards |
endDate |
String (dd/mm/yyyy) | Events up to this date |
limit |
Integer (default: 500) | Maximum number of events returned |
{
"id": 42,
"timestamp": 1692000000,
"timestampFormatted": "15/08/2023 14:00:00",
"logLevelCode": 3,
"logLevel": "INFO",
"description": "User admin logged in",
"source": "login",
"userID": 1,
"userName": "admin",
"userDisplayName": "Administrator",
"transactionID": 12345,
"sessionID": "qun27mjdmgqeerpm1ds5gn0lfk",
"objectID": 1,
"objectType": "users",
"activityCode": "login"
}| Method | Endpoint | Description |
|---|---|---|
| GET | /events/stats/byLevel |
Returns event counts grouped by log level |
| GET | /events/stats/perDay |
Returns daily event counts for the last 30 days |
| Method | Endpoint | Description |
|---|---|---|
| DELETE | /events |
Delete events. Body: {"logLevel": [1,2]} to delete specific levels, or {} to delete all. Requires events_mgt right. Writes an AUDIT log entry after deletion (audit_all_events_deleted / audit_events_with_level_deleted, activity DELETE, object type events). |
The Event Viewer uses the standard Dashio color palette:
| Element | Color | Hex |
|---|---|---|
| Header background | Teal | #4ECDC4 |
| Filter bar background | Dark charcoal | #22242a |
| AUDIT badge | Teal | #4ECDC4 |
| ERROR badge | Red | #e6605e |
| WARNING badge | Amber | #f0ad4e |
| INFO badge | Blue | #3498db |
| DEBUG badge | Gray | #8f8f8f |
| L18N badge | Purple | #9b59b6 |
| Active page indicator | Teal | #4ECDC4 |
| File | Purpose |
|---|---|
gui/templates/eventviewer/eventviewer.html |
Frontend page (HTML + JS + CSS, no PHP) |
api/eventviewer/index.php |
BFF API (plain PHP, no framework) |
gui/templates/dashio/lib/tl-line-tooltip.js |
Hover-tooltip shim for the line chart (mirror of tl-pie-tooltip.js) |