Show the analytics preview in telemetry settings

This commit is contained in:
Josh Hawkins
2026-09-22 08:31:00 -05:00
parent f185e64379
commit 3990a64988
10 changed files with 137 additions and 4 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,54 @@
/**
* Telemetry settings: the analytics preview shows the report Frigate would
* send, fetched only when opened.
*/
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { test, expect } from "../../fixtures/frigate-test";
const __dirname = dirname(fileURLToPath(import.meta.url));
const CONFIG_SCHEMA = JSON.parse(
readFileSync(
resolve(__dirname, "../../fixtures/mock-data/config-schema.json"),
"utf-8",
),
);
const PREVIEW = {
schema_version: 1,
install_id: "0".repeat(32),
install: { version: "0.19.0-test" },
};
test.describe("telemetry settings: analytics preview @medium", () => {
test("shows the report on demand", async ({ frigateApp }) => {
let requests = 0;
const page = frigateApp.page;
await page.route("**/api/config/schema.json", (route) =>
route.fulfill({ json: CONFIG_SCHEMA }),
);
await page.route("**/api/config/raw_paths", (route) =>
route.fulfill({ json: { telemetry: {} } }),
);
await page.route("**/api/analytics/preview", (route) => {
requests += 1;
return route.fulfill({ json: PREVIEW });
});
await frigateApp.goto("/settings?page=systemTelemetry");
const button = page.getByRole("button", { name: "Preview the report" });
await expect(button).toBeVisible();
expect(requests).toBe(0);
await button.click();
await expect(page.getByTestId("analytics-preview")).toContainText(
'"schema_version": 1',
);
expect(requests).toBe(1);
});
});
+4
View File
@@ -222,6 +222,10 @@
"telemetry": {
"label": "Telemetry",
"description": "System telemetry and stats options including GPU and network bandwidth monitoring.",
"analytics": {
"label": "Share anonymous analytics",
"description": "Send one anonymous usage report a day to help the Frigate maintainers decide what to support. Nothing is sent until this is on."
},
"network_interfaces": {
"label": "Network interfaces",
"description": "List of network interface name prefixes to monitor for bandwidth statistics."
@@ -1,4 +1,9 @@
{
"analyticsPreview": {
"show": "Preview the report",
"desc": "Frigate sends this report once a day while sharing is on. It never includes camera names, zones, labels, or addresses.",
"error": "Unable to build the preview"
},
"documentTitle": {
"default": "Settings - Frigate",
"authentication": "Authentication Settings - Frigate",
+2 -1
View File
@@ -45,7 +45,8 @@
"shm_too_low": "/dev/shm allocation ({{total}} MB) should be increased to at least {{min}} MB",
"failed_login_one": "Failed login attempt for {{user}}",
"failed_login_other": "Failed login attempts for {{user}}",
"update_available": "Frigate {{version}} is available"
"update_available": "Frigate {{version}} is available",
"analytics_prompt": "Share anonymous usage statistics to help improve Frigate"
},
"streamPrefix": "Stream {{index}}: {{message}}",
"streamPrefixRestream": "Stream {{index}} (via go2rtc): {{message}}",
@@ -3,9 +3,17 @@ import type { SectionConfigOverrides } from "./types";
const telemetry: SectionConfigOverrides = {
base: {
sectionDocs: "/configuration/advanced/reference",
fieldDocs: {
analytics: "/configuration/advanced/analytics",
},
restartRequired: ["version_check"],
fieldOrder: ["network_interfaces", "stats", "version_check"],
fieldOrder: ["analytics", "network_interfaces", "stats", "version_check"],
advancedFields: [],
uiSchema: {
analytics: {
"ui:after": { render: "AnalyticsPreview" },
},
},
},
};
@@ -0,0 +1,56 @@
import { useState } from "react";
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import { LuChevronDown, LuChevronRight } from "react-icons/lu";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
export default function AnalyticsPreview() {
const { t } = useTranslation("views/settings");
const [open, setOpen] = useState(false);
// built on demand, since building runs every collector on the server
const { data, error } = useSWR<Record<string, unknown>>(
open ? "analytics/preview" : null,
{ revalidateOnFocus: false },
);
return (
<Collapsible open={open} onOpenChange={setOpen} className="mt-2">
<CollapsibleTrigger asChild>
<Button variant="outline" size="sm" className="gap-1">
{open ? (
<LuChevronDown className="size-4" />
) : (
<LuChevronRight className="size-4" />
)}
{t("analyticsPreview.show")}
</Button>
</CollapsibleTrigger>
<div className="mt-1 text-xs text-muted-foreground">
{t("analyticsPreview.desc")}
</div>
<CollapsibleContent className="mt-2">
{error ? (
<div className="text-sm text-danger">
{t("analyticsPreview.error")}
</div>
) : data ? (
<pre
data-testid="analytics-preview"
className="max-h-96 overflow-auto rounded-md bg-secondary p-3 text-xs"
>
{JSON.stringify(data, null, 2)}
</pre>
) : (
<ActivityIndicator className="size-5" />
)}
</CollapsibleContent>
</Collapsible>
);
}
@@ -1,4 +1,5 @@
import type { ComponentType } from "react";
import AnalyticsPreview from "./AnalyticsPreview";
import SemanticSearchReindex from "./SemanticSearchReindex.tsx";
import CameraReviewStatusToggles from "./CameraReviewStatusToggles";
import ProxyRoleMap from "./ProxyRoleMap";
@@ -56,6 +57,9 @@ export const sectionRenderers: SectionRenderers = {
birdseye: {
BirdseyeCameraReorder,
},
telemetry: {
AnalyticsPreview,
},
};
export default sectionRenderers;
+1
View File
@@ -649,6 +649,7 @@ export interface FrigateConfig {
};
telemetry: {
analytics: boolean;
network_interfaces: string[];
stats: {
amd_gpu_stats: boolean;