Add runtime versions summary to system page#231
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
This PR adds a runtime-generated versions summary to GET /api/system/info and surfaces it in a new "Versions" tab on the System page. It collects version information for LightNVR itself, the base OS, optional services (go2rtc), and all linked libraries (SQLite, libcurl, mbedTLS, libuv, llhttp, FFmpeg components).
Changes:
- New C helper functions in
api_handlers_system.cto collect and serialize runtime library/OS versions into theversions.itemsJSON array - Extended
go2rtc_api.c/go2rtc_api.hwith ago2rtc_api_get_application_infofunction that also retrieves go2rtc's version and revision strings - New
VersionsTable.jsxfrontend component and a tab-based layout inSystemView.jsx, backed by updated integration tests and API documentation
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/web/api_handlers_system.c |
Adds add_versions_to_json, read_os_release_value, trim_copy_value, and format_triplet_version helpers; calls add_versions_to_json in handle_get_system_info |
src/video/go2rtc/go2rtc_api.c |
Refactors go2rtc_api_get_server_info into go2rtc_api_get_application_info with version/revision output params; original function delegates to the new one |
include/video/go2rtc/go2rtc_api.h |
Declares the new go2rtc_api_get_application_info function |
web/js/components/preact/system/VersionsTable.jsx |
New component rendering the versions array in a categorized table |
web/js/components/preact/SystemView.jsx |
Adds tab navigation between System and Versions panels; initializes versions.items state |
tests/integration/specs/system.ui.spec.ts |
New UI integration test for the Versions tab |
tests/integration/pages/SystemPage.ts |
Adds locators and openVersionsTab helper for the new UI |
tests/integration/lightnvr.api.spec.ts |
Extends the system API test to verify versions.items structure |
docs/API.md |
Documents the new versions.items field in the system info response |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| bool go2rtc_api_get_application_info(int *rtsp_port, | ||
| char *version, size_t version_size, | ||
| char *revision, size_t revision_size) { | ||
| if (!g_initialized) { | ||
| log_error("go2rtc API client not initialized"); | ||
| return false; | ||
| } | ||
|
|
||
| if (version && version_size > 0) { | ||
| version[0] = '\0'; | ||
| } | ||
| if (revision && revision_size > 0) { |
There was a problem hiding this comment.
The go2rtc_api_get_application_info function does not set CURLOPT_TIMEOUT on the CURL handle, unlike other functions in the same file (e.g., go2rtc_api_add_stream and go2rtc_api_stream_exists both set CURLOPT_TIMEOUT to 10s). Since add_versions_to_json calls this function on every GET /api/system/info request, a non-responsive go2rtc process will cause the entire system info API call to hang indefinitely until the OS-level TCP timeout fires. A CURLOPT_TIMEOUT of a few seconds (e.g., 5s) should be added to match the pattern used elsewhere in this file.
| {activeTab === 'system' ? ( | ||
| <> | ||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> | ||
| <SystemInfo systemInfo={systemInfo} formatUptime={formatUptime} /> | ||
| <MemoryStorage systemInfo={systemInfo} formatBytes={formatBytes} /> | ||
| </div> | ||
|
|
||
| <div className="mb-4"> | ||
| <StreamStorage systemInfo={systemInfo} formatBytes={formatBytes} /> | ||
| </div> | ||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> | ||
| <StreamsInfo systemInfo={systemInfo} formatBytes={formatBytes} /> | ||
| <StorageHealth formatBytes={formatBytes} /> | ||
| </div> | ||
|
|
||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> | ||
| <NetworkInfo systemInfo={systemInfo} /> | ||
| <WebServiceInfo systemInfo={systemInfo} /> | ||
| </div> | ||
| <div className="mb-4"> | ||
| <StreamStorage systemInfo={systemInfo} formatBytes={formatBytes} /> | ||
| </div> | ||
|
|
||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> | ||
| <NetworkInfo systemInfo={systemInfo} /> | ||
| <WebServiceInfo systemInfo={systemInfo} /> | ||
| </div> | ||
|
|
||
| <LogsView | ||
| logs={logs} | ||
| logLevel={logLevel} | ||
| logCount={logCount} | ||
| pollingInterval={pollingInterval} | ||
| setLogLevel={handleSetLogLevel} | ||
| setLogCount={setLogCount} | ||
| setPollingInterval={handleSetPollingInterval} | ||
| loadLogs={() => { | ||
| // Trigger a manual log refresh | ||
| console.log('Manually triggering log refresh'); | ||
| const event = new CustomEvent('refresh-logs'); | ||
| window.dispatchEvent(event); | ||
| }} | ||
| clearLogs={clearLogs} | ||
| /> | ||
| <LogsView | ||
| logs={logs} | ||
| logLevel={logLevel} | ||
| logCount={logCount} | ||
| pollingInterval={pollingInterval} | ||
| setLogLevel={handleSetLogLevel} | ||
| setLogCount={setLogCount} | ||
| setPollingInterval={handleSetPollingInterval} | ||
| loadLogs={() => { | ||
| // Trigger a manual log refresh | ||
| console.log('Manually triggering log refresh'); | ||
| const event = new CustomEvent('refresh-logs'); | ||
| window.dispatchEvent(event); | ||
| }} | ||
| clearLogs={clearLogs} | ||
| /> | ||
|
|
||
| <LogsPoller | ||
| logLevel={logLevel} | ||
| logCount={logCount} | ||
| pollingInterval={pollingInterval} | ||
| onLogsReceived={handleLogsReceived} | ||
| /> | ||
| <LogsPoller | ||
| logLevel={logLevel} | ||
| logCount={logCount} | ||
| pollingInterval={pollingInterval} | ||
| onLogsReceived={handleLogsReceived} | ||
| /> | ||
| </> | ||
| ) : ( | ||
| <VersionsTable versions={systemInfo.versions} /> | ||
| )} |
There was a problem hiding this comment.
The tab content panel (the div containing the System or Versions content) lacks a role="tabpanel" attribute and the associated aria-labelledby attribute linking it to the active tab button. For proper ARIA tab pattern compliance, the content container should have role="tabpanel" and be associated with the controlling tab via aria-labelledby. The active tab button should also have aria-controls pointing to the panel id. Without these, screen readers cannot properly announce the tab structure or identify the active panel.
Summary
versions.itemssummary toGET /api/system/infoVersionstab on the System pageCloses #229.
Validation
cmake --build build --target lightnvrnpm --prefix web run buildnpx playwright test tests/integration/lightnvr.api.spec.ts --project=apinpx playwright test tests/integration/specs/system.ui.spec.ts --project=ui --grep "versions summary in a dedicated tab"Pull Request opened by Augment Code with guidance from the PR author