diff --git a/frigate/stats/emitter.py b/frigate/stats/emitter.py index 5ae8181d9..a2d04a2ae 100644 --- a/frigate/stats/emitter.py +++ b/frigate/stats/emitter.py @@ -59,7 +59,7 @@ class StatsEmitter(threading.Thread): selected = {} for k in keys: - selected[k] = s[k] + selected[k] = s.get(k) selected_stats.append(selected) @@ -67,7 +67,7 @@ class StatsEmitter(threading.Thread): def run(self) -> None: time.sleep(10) - for counter in itertools.cycle(range(int(self.config.record.expire_interval / 10))): + for counter in itertools.cycle(range(int(self.config.mqtt.stats_interval / 10))): if self.stop_event.wait(10): break diff --git a/web/src/components/graph/SystemGraph.tsx b/web/src/components/graph/SystemGraph.tsx index e69a6c3e7..4b8dd8045 100644 --- a/web/src/components/graph/SystemGraph.tsx +++ b/web/src/components/graph/SystemGraph.tsx @@ -1,5 +1,5 @@ import { Threshold } from "@/types/graph"; -import { useMemo } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import Chart from "react-apexcharts"; type SystemGraphProps = { @@ -7,6 +7,7 @@ type SystemGraphProps = { name: string; unit: string; threshold: Threshold; + updateTimes: number[]; data: ApexAxisChartSeries; }; export default function SystemGraph({ @@ -14,6 +15,7 @@ export default function SystemGraph({ name, unit, threshold, + updateTimes, data, }: SystemGraphProps) { const lastValue = useMemo( @@ -22,6 +24,79 @@ export default function SystemGraph({ [data], ); + const formatTime = useCallback( + (val: unknown) => { + console.log( + `inside the check we have ${updateTimes.length} times and we are looking for ${val}`, + ); + const date = new Date(updateTimes[Math.round(val as number)] * 1000); + return `${date.getHours() > 12 ? date.getHours() - 12 : date.getHours()}:${date.getMinutes()}`; + }, + [updateTimes], + ); + + const options = useMemo(() => { + return { + chart: { + id: graphId, + selection: { + enabled: false, + }, + toolbar: { + show: false, + }, + zoom: { + enabled: false, + }, + }, + colors: [ + ({ value }: { value: number }) => { + if (value >= threshold.error) { + return "#FA5252"; + } else if (value >= threshold.warning) { + return "#FF9966"; + } else { + return "#404040"; + } + }, + ], + grid: { + show: false, + }, + legend: { + show: false, + }, + dataLabels: { + enabled: false, + }, + plotOptions: { + bar: { + distributed: true, + }, + }, + xaxis: { + tickAmount: 6, + labels: { + formatter: formatTime, + }, + axisBorder: { + show: false, + }, + axisTicks: { + show: false, + }, + }, + yaxis: { + show: false, + max: lastValue * 2, + }, + }; + }, [graphId, lastValue, threshold, formatTime]); + + useEffect(() => { + ApexCharts.exec(graphId, "updateOptions", options, true, true); + }, [graphId, options]); + return (
@@ -31,62 +106,7 @@ export default function SystemGraph({ {unit}
- { - if (value >= threshold.error) { - return "#FA5252"; - } else if (value >= threshold.warning) { - return "#FF9966"; - } else { - return "#404040"; - } - }, - ], - grid: { - show: false, - }, - legend: { - show: false, - }, - dataLabels: { - enabled: false, - }, - xaxis: { - type: "datetime", - axisBorder: { - show: false, - }, - axisTicks: { - show: false, - }, - labels: { - format: "h:mm", - datetimeUTC: false, - }, - }, - yaxis: { - show: false, - max: lastValue * 2, - }, - }} - series={data} - height="120" - /> + ); } diff --git a/web/src/components/overlay/VainfoDialog.tsx b/web/src/components/overlay/VainfoDialog.tsx new file mode 100644 index 000000000..51cd78f33 --- /dev/null +++ b/web/src/components/overlay/VainfoDialog.tsx @@ -0,0 +1,57 @@ +import useSWR from "swr"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "../ui/dialog"; +import ActivityIndicator from "../indicators/activity-indicator"; +import { Vainfo } from "@/types/stats"; +import { Button } from "../ui/button"; +import copy from "copy-to-clipboard"; + +type VainfoDialogProps = { + showVainfo: boolean; + setShowVainfo: (show: boolean) => void; +}; +export default function VainfoDialog({ + showVainfo, + setShowVainfo, +}: VainfoDialogProps) { + const { data: vainfo } = useSWR(showVainfo ? "vainfo" : null); + + const onCopyVainfo = async () => { + copy(JSON.stringify(vainfo).replace(/[\\\s]+/gi, "")); + setShowVainfo(false); + }; + + return ( + + + + Vainfo Output + + {vainfo ? ( +
+
Return Code: {vainfo.return_code}
+
+
Process {vainfo.return_code == 0 ? "Output" : "Error"}:
+
+
{vainfo.return_code == 0 ? vainfo.stdout : vainfo.stderr}
+
+ ) : ( + + )} + + + + +
+
+ ); +} diff --git a/web/src/pages/System.tsx b/web/src/pages/System.tsx index 30ec629b4..b8343e1d0 100644 --- a/web/src/pages/System.tsx +++ b/web/src/pages/System.tsx @@ -8,263 +8,31 @@ import { FrigateConfig } from "@/types/frigateConfig"; import { DetectorCpuThreshold, DetectorMemThreshold, + GPUMemThreshold, + GPUUsageThreshold, InferenceThreshold, } from "@/types/graph"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { Button } from "@/components/ui/button"; +import VainfoDialog from "@/components/overlay/VainfoDialog"; const metrics = ["general", "storage", "cameras"] as const; type SystemMetric = (typeof metrics)[number]; function System() { - const { data: config } = useSWR("config"); - // stats page const [page, setPage] = useState("general"); + const [lastUpdated, setLastUpdated] = useState(Date.now() / 1000); // stats collection - const { data: initialStats } = useSWR("stats/history", { + const { data: statsSnapshot } = useSWR("stats", { revalidateOnFocus: false, }); - const { payload: updatedStats } = useFrigateStats(); - const [statsHistory, setStatsHistory] = useState( - initialStats || [], - ); - - const lastUpdated = useMemo(() => { - if (updatedStats) { - return updatedStats.service.last_updated; - } - - if (initialStats) { - return initialStats.at(-1)?.service?.last_updated; - } - - return undefined; - }, [initialStats, updatedStats]); - - useEffect(() => { - if (initialStats == undefined) { - return; - } - - if (statsHistory.length < initialStats.length) { - setStatsHistory(initialStats); - return; - } - - setStatsHistory([...statsHistory, updatedStats]); - }, [initialStats, updatedStats]); - - // stats data pieces - - const gpuSeries = useMemo(() => { - if (!statsHistory) { - return []; - } - - const series: { - [key: string]: { name: string; data: { x: object; y: string }[] }; - } = {}; - - statsHistory.forEach((stats) => { - if (!stats) { - return; - } - - const statTime = new Date(stats.service.last_updated * 1000); - - Object.entries(stats.gpu_usages || []).forEach(([key, stats]) => { - if (!(key in series)) { - series[key] = { name: key, data: [] }; - } - - series[key].data.push({ x: statTime, y: stats.gpu }); - }); - }); - return Object.keys(series).length > 0 ? Object.values(series) : []; - }, [statsHistory]); - const gpuMemSeries = useMemo(() => { - if (!statsHistory) { - return []; - } - - const series: { - [key: string]: { name: string; data: { x: object; y: string }[] }; - } = {}; - - statsHistory.forEach((stats) => { - if (!stats) { - return; - } - - const statTime = new Date(stats.service.last_updated * 1000); - - Object.entries(stats.gpu_usages || {}).forEach(([key, stats]) => { - if (!(key in series)) { - series[key] = { name: key, data: [] }; - } - - series[key].data.push({ x: statTime, y: stats.mem }); - }); - }); - return Object.values(series); - }, [statsHistory]); - const cameraCpuSeries = useMemo(() => { - if (!statsHistory || statsHistory.length == 0) { - return {}; - } - - const series: { - [cam: string]: { - [key: string]: { name: string; data: { x: object; y: string }[] }; - }; - } = {}; - - statsHistory.forEach((stats) => { - if (!stats) { - return; - } - - const statTime = new Date(stats.service.last_updated * 1000); - - Object.entries(stats.cameras).forEach(([key, camStats]) => { - if (!config?.cameras[key].enabled) { - return; - } - - if (!(key in series)) { - const camName = key.replaceAll("_", " "); - series[key] = {}; - series[key]["ffmpeg"] = { name: `${camName} ffmpeg`, data: [] }; - series[key]["capture"] = { name: `${camName} capture`, data: [] }; - series[key]["detect"] = { name: `${camName} detect`, data: [] }; - } - - series[key]["ffmpeg"].data.push({ - x: statTime, - y: stats.cpu_usages[camStats.ffmpeg_pid.toString()]?.cpu ?? 0.0, - }); - series[key]["capture"].data.push({ - x: statTime, - y: stats.cpu_usages[camStats.capture_pid?.toString()]?.cpu ?? 0, - }); - series[key]["detect"].data.push({ - x: statTime, - y: stats.cpu_usages[camStats.pid.toString()].cpu, - }); - }); - }); - return series; - }, [statsHistory]); - const cameraFpsSeries = useMemo(() => { - if (!statsHistory) { - return {}; - } - - const series: { - [cam: string]: { - [key: string]: { name: string; data: { x: object; y: number }[] }; - }; - } = {}; - - statsHistory.forEach((stats) => { - if (!stats) { - return; - } - - const statTime = new Date(stats.service.last_updated * 1000); - - Object.entries(stats.cameras).forEach(([key, camStats]) => { - if (!(key in series)) { - const camName = key.replaceAll("_", " "); - series[key] = {}; - series[key]["det"] = { name: `${camName} detections`, data: [] }; - series[key]["skip"] = { - name: `${camName} skipped detections`, - data: [], - }; - } - - series[key]["det"].data.push({ - x: statTime, - y: camStats.detection_fps, - }); - series[key]["skip"].data.push({ - x: statTime, - y: camStats.skipped_fps, - }); - }); - }); - return series; - }, [statsHistory]); - const otherProcessCpuSeries = useMemo(() => { - if (!statsHistory) { - return []; - } - - const series: { - [key: string]: { name: string; data: { x: object; y: string }[] }; - } = {}; - - statsHistory.forEach((stats) => { - if (!stats) { - return; - } - - const statTime = new Date(stats.service.last_updated * 1000); - - Object.entries(stats.processes).forEach(([key, procStats]) => { - if (procStats.pid.toString() in stats.cpu_usages) { - if (!(key in series)) { - series[key] = { name: `${key} (${procStats.pid})`, data: [] }; - } - - series[key].data.push({ - x: statTime, - y: stats.cpu_usages[procStats.pid.toString()].cpu, - }); - } - }); - }); - return Object.keys(series).length > 0 ? Object.values(series) : []; - }, [statsHistory]); - const otherProcessMemSeries = useMemo(() => { - if (!statsHistory) { - return []; - } - - const series: { - [key: string]: { name: string; data: { x: object; y: string }[] }; - } = {}; - - statsHistory.forEach((stats) => { - if (!stats) { - return; - } - - const statTime = new Date(stats.service.last_updated * 1000); - - Object.entries(stats.processes).forEach(([key, procStats]) => { - if (procStats.pid.toString() in stats.cpu_usages) { - if (!(key in series)) { - series[key] = { name: key, data: [] }; - } - - series[key].data.push({ - x: statTime, - y: stats.cpu_usages[procStats.pid.toString()].mem, - }); - } - }); - }); - return Object.values(series); - }, [statsHistory]); return ( -
+
System
- {initialStats && ( + {statsSnapshot && (
- {initialStats[0].service.version} + {statsSnapshot.service.version}
)}
- {page == "general" && } + {page == "general" && ( + + )}
); } @@ -313,59 +86,96 @@ function System() { export default System; /** - *
+ * const cameraCpuSeries = useMemo(() => { + if (!statsHistory || statsHistory.length == 0) { + return {}; + } -
-
- -
+ const series: { + [cam: string]: { + [key: string]: { name: string; data: { x: object; y: string }[] }; + }; + } = {}; + + statsHistory.forEach((stats, statsIdx) => { + if (!stats) { + return; + } + + const statTime = new Date(stats.service.last_updated * 1000); + + Object.entries(stats.cameras).forEach(([key, camStats]) => { + if (!config?.cameras[key].enabled) { + return; + } + + if (!(key in series)) { + const camName = key.replaceAll("_", " "); + series[key] = {}; + series[key]["ffmpeg"] = { name: `${camName} ffmpeg`, data: [] }; + series[key]["capture"] = { name: `${camName} capture`, data: [] }; + series[key]["detect"] = { name: `${camName} detect`, data: [] }; + } + + series[key]["ffmpeg"].data.push({ + x: statsIdx, + y: stats.cpu_usages[camStats.ffmpeg_pid.toString()]?.cpu ?? 0.0, + }); + series[key]["capture"].data.push({ + x: statsIdx, + y: stats.cpu_usages[camStats.capture_pid?.toString()]?.cpu ?? 0, + }); + series[key]["detect"].data.push({ + x: statsIdx, + y: stats.cpu_usages[camStats.pid.toString()].cpu, + }); + }); + }); + return series; + }, [statsHistory]); + const cameraFpsSeries = useMemo(() => { + if (!statsHistory) { + return {}; + } + + const series: { + [cam: string]: { + [key: string]: { name: string; data: { x: object; y: number }[] }; + }; + } = {}; + + statsHistory.forEach((stats, statsIdx) => { + if (!stats) { + return; + } + + const statTime = new Date(stats.service.last_updated * 1000); + + Object.entries(stats.cameras).forEach(([key, camStats]) => { + if (!(key in series)) { + const camName = key.replaceAll("_", " "); + series[key] = {}; + series[key]["det"] = { name: `${camName} detections`, data: [] }; + series[key]["skip"] = { + name: `${camName} skipped detections`, + data: [], + }; + } + + series[key]["det"].data.push({ + x: statsIdx, + y: camStats.detection_fps, + }); + series[key]["skip"].data.push({ + x: statsIdx, + y: camStats.skipped_fps, + }); + }); + }); + return series; + }, [statsHistory]); * - * Detectors -
- - - -
- {gpuSeries.length > 0 && ( - <> - GPUs -
- - -
- - )} + *
Cameras
{config && @@ -392,28 +202,21 @@ export default System; return null; })}
- Other Processes -
- - -
*/ -function GeneralMetrics() { +type GeneralMetricsProps = { + lastUpdated: number; + setLastUpdated: (last: number) => void; +}; +function GeneralMetrics({ lastUpdated, setLastUpdated }: GeneralMetricsProps) { + // extra info + + const [showVainfo, setShowVainfo] = useState(false); + // stats const { data: initialStats } = useSWR( - ["stats/history", { keys: "cpu_usages,detectors,service" }], + ["stats/history", { keys: "cpu_usages,detectors,processes,service" }], { revalidateOnFocus: false, }, @@ -423,19 +226,33 @@ function GeneralMetrics() { const { payload: updatedStats } = useFrigateStats(); useEffect(() => { - if (initialStats == undefined) { + if (initialStats == undefined || initialStats.length == 0) { return; } - if (statsHistory.length < initialStats.length) { + if (statsHistory.length == 0) { setStatsHistory(initialStats); return; } - setStatsHistory([...statsHistory, updatedStats]); - }, [initialStats, updatedStats]); + if (!updatedStats) { + return; + } - // stats data pieces + if (updatedStats.service.last_updated > lastUpdated) { + setStatsHistory([...statsHistory, updatedStats]); + setLastUpdated(Date.now() / 1000); + } + }, [initialStats, updatedStats, statsHistory, lastUpdated, setLastUpdated]); + + // timestamps + + const updateTimes = useMemo( + () => statsHistory.map((stats) => stats.service.last_updated), + [statsHistory], + ); + + // detectors stats const detInferenceTimeSeries = useMemo(() => { if (!statsHistory) { @@ -443,78 +260,74 @@ function GeneralMetrics() { } const series: { - [key: string]: { name: string; data: { x: object; y: number }[] }; + [key: string]: { name: string; data: { x: number; y: number }[] }; } = {}; - statsHistory.forEach((stats) => { + statsHistory.forEach((stats, statsIdx) => { if (!stats) { return; } - const statTime = new Date(stats.service.last_updated * 1000); - Object.entries(stats.detectors).forEach(([key, stats]) => { if (!(key in series)) { - series[key] = { name: `${key} (${stats.pid})`, data: [] }; + series[key] = { name: key, data: [] }; } - series[key].data.push({ x: statTime, y: stats.inference_speed }); + series[key].data.push({ x: statsIdx, y: stats.inference_speed }); }); }); return Object.values(series); }, [statsHistory]); + const detCpuSeries = useMemo(() => { if (!statsHistory) { return []; } const series: { - [key: string]: { name: string; data: { x: object; y: string }[] }; + [key: string]: { name: string; data: { x: number; y: string }[] }; } = {}; - statsHistory.forEach((stats) => { + statsHistory.forEach((stats, statsIdx) => { if (!stats) { return; } - const statTime = new Date(stats.service.last_updated * 1000); - Object.entries(stats.detectors).forEach(([key, detStats]) => { if (!(key in series)) { series[key] = { name: key, data: [] }; } series[key].data.push({ - x: statTime, + x: statsIdx, y: stats.cpu_usages[detStats.pid.toString()].cpu, }); }); }); return Object.values(series); }, [statsHistory]); + const detMemSeries = useMemo(() => { if (!statsHistory) { return []; } const series: { - [key: string]: { name: string; data: { x: object; y: string }[] }; + [key: string]: { name: string; data: { x: number; y: string }[] }; } = {}; - statsHistory.forEach((stats) => { + statsHistory.forEach((stats, statsIdx) => { if (!stats) { return; } - const statTime = new Date(stats.service.last_updated * 1000); - Object.entries(stats.detectors).forEach(([key, detStats]) => { if (!(key in series)) { series[key] = { name: key, data: [] }; } series[key].data.push({ - x: statTime, + x: statsIdx, y: stats.cpu_usages[detStats.pid.toString()].mem, }); }); @@ -522,108 +335,270 @@ function GeneralMetrics() { return Object.values(series); }, [statsHistory]); + // gpu stats + + const gpuSeries = useMemo(() => { + if (!statsHistory) { + return []; + } + + const series: { + [key: string]: { name: string; data: { x: number; y: string }[] }; + } = {}; + + statsHistory.forEach((stats, statsIdx) => { + if (!stats) { + return; + } + + Object.entries(stats.gpu_usages || []).forEach(([key, stats]) => { + if (!(key in series)) { + series[key] = { name: key, data: [] }; + } + + series[key].data.push({ x: statsIdx, y: stats.gpu }); + }); + }); + return Object.keys(series).length > 0 ? Object.values(series) : []; + }, [statsHistory]); + + const gpuMemSeries = useMemo(() => { + if (!statsHistory) { + return []; + } + + const series: { + [key: string]: { name: string; data: { x: number; y: string }[] }; + } = {}; + + statsHistory.forEach((stats, statsIdx) => { + if (!stats) { + return; + } + + Object.entries(stats.gpu_usages || {}).forEach(([key, stats]) => { + if (!(key in series)) { + series[key] = { name: key, data: [] }; + } + + series[key].data.push({ x: statsIdx, y: stats.mem }); + }); + }); + return Object.values(series); + }, [statsHistory]); + + // other processes stats + + const otherProcessCpuSeries = useMemo(() => { + if (!statsHistory) { + return []; + } + + const series: { + [key: string]: { name: string; data: { x: number; y: string }[] }; + } = {}; + + statsHistory.forEach((stats, statsIdx) => { + if (!stats) { + return; + } + + Object.entries(stats.processes).forEach(([key, procStats]) => { + if (procStats.pid.toString() in stats.cpu_usages) { + if (!(key in series)) { + series[key] = { name: key, data: [] }; + } + + series[key].data.push({ + x: statsIdx, + y: stats.cpu_usages[procStats.pid.toString()].cpu, + }); + } + }); + }); + return Object.keys(series).length > 0 ? Object.values(series) : []; + }, [statsHistory]); + + const otherProcessMemSeries = useMemo(() => { + if (!statsHistory) { + return []; + } + + const series: { + [key: string]: { name: string; data: { x: number; y: string }[] }; + } = {}; + + statsHistory.forEach((stats, statsIdx) => { + if (!stats) { + return; + } + + Object.entries(stats.processes).forEach(([key, procStats]) => { + if (procStats.pid.toString() in stats.cpu_usages) { + if (!(key in series)) { + series[key] = { name: key, data: [] }; + } + + series[key].data.push({ + x: statsIdx, + y: stats.cpu_usages[procStats.pid.toString()].mem, + }); + } + }); + }); + return Object.values(series); + }, [statsHistory]); + + if (statsHistory.length == 0) { + return; + } + return ( -
-
Detectors
-
-
-
Detector Inference Speed
- {detInferenceTimeSeries.map((series) => ( - - ))} + <> + + +
+
+ Detectors
-
-
Detector CPU Usage
- {detCpuSeries.map((series) => ( - - ))} +
+
+
Detector Inference Speed
+ {detInferenceTimeSeries.map((series) => ( + + ))} +
+
+
Detector CPU Usage
+ {detCpuSeries.map((series) => ( + + ))} +
+
+
Detector Memory Usage
+ {detMemSeries.map((series) => ( + + ))} +
-
-
Detector Memory Usage
- {detMemSeries.map((series) => ( - - ))} + + {statsHistory.length > 0 && statsHistory[0].gpu_usages && ( + <> +
+
+ Detectors +
+ {Object.keys(statsHistory[0].gpu_usages).filter( + (key) => + key == "amd-vaapi" || + key == "intel-vaapi" || + key == "intel-qsv", + ).length > 0 && ( + + )} +
+
+
+
GPU Usage
+ {gpuSeries.map((series) => ( + + ))} +
+
+
GPU Memory
+ {gpuMemSeries.map((series) => ( + + ))} +
+
+ + )} + +
+ Other Processes +
+
+
+
Process CPU Usage
+ {otherProcessCpuSeries.map((series) => ( + + ))} +
+
+
Process Memory Usage
+ {otherProcessMemSeries.map((series) => ( + + ))} +
- - {statsHistory.length > 0 && statsHistory[0].gpu_usages && ( - <> -
-
- Detectors -
- {Object.keys(statsHistory[0].gpu_usages).filter( - (key) => - key == "amd-vaapi" || - key == "intel-vaapi" || - key == "intel-qsv", - ).length > 0 && } -
-
-
-
Detector Inference Speed
- {detInferenceTimeSeries.map((series) => ( - - ))} -
- -
-
Detector CPU Usage
- {detCpuSeries.map((series) => ( - - ))} -
-
-
Detector Memory Usage
- {detMemSeries.map((series) => ( - - ))} -
-
- - )} -
+ ); } + +function CameraMetrics() { + const { data: config } = useSWR("config"); +} diff --git a/web/src/types/graph.ts b/web/src/types/graph.ts index cff87f6f3..4963142e8 100644 --- a/web/src/types/graph.ts +++ b/web/src/types/graph.ts @@ -28,6 +28,16 @@ export const DetectorMemThreshold = { error: 50, } as Threshold; +export const GPUUsageThreshold = { + warning: 75, + error: 95, +} as Threshold; + +export const GPUMemThreshold = { + warning: 75, + error: 95, +} as Threshold; + export const CameraFfmpegThreshold = { warning: 20, error: 20, diff --git a/web/src/types/stats.ts b/web/src/types/stats.ts index 831e2e639..3b293f76a 100644 --- a/web/src/types/stats.ts +++ b/web/src/types/stats.ts @@ -63,3 +63,9 @@ export type PotentialProblem = { text: string; color: string; }; + +export type Vainfo = { + return_code: number; + stdout: string; + stderr: string; +}; diff --git a/web/vite.config.ts b/web/vite.config.ts index cc3ead707..5afefa331 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -12,24 +12,24 @@ export default defineConfig({ server: { proxy: { "/api": { - target: "http://192.168.50.106:5000", + target: "http://localhost:5000", ws: true, }, "/vod": { - target: "http://192.168.50.106:5000", + target: "http://localhost:5000", }, "/clips": { - target: "http://192.168.50.106:5000", + target: "http://localhost:5000", }, "/exports": { - target: "http://192.168.50.106:5000", + target: "http://localhost:5000", }, "/ws": { - target: "ws://192.168.50.106:5000", + target: "ws://localhost:5000", ws: true, }, "/live": { - target: "ws://192.168.50.106:5000", + target: "ws://localhost:5000", changeOrigin: true, ws: true, },