Compare commits

..

20 Commits

Author SHA1 Message Date
Nicolas Mowen
cf2466c8c1 Fix recordings storage (#5031)
* Fix recordings storage

* Update record.py

* Update record.py

* Formatting
2023-01-12 05:53:38 -06:00
James L
6b123675c4 System page: add detector process mem% (#5028) 2023-01-11 18:11:45 -06:00
James L
ddcae2d4aa Fix mem usage reporting when using docker limits (#5011)
* Fix mem usage reporting when using docker limits

* format code

* wip
2023-01-11 17:39:26 -06:00
sergeknystautas
731db8fb8f Add in_progress parameter to /api/events to filter the results. (#5013)
* Add in_progress parameter to /api/events to filter the results.

* Change in_progress to default to no filtering, 0 means no in progress, 1 means only in progress.

* Fix code format with black.

* Clear blank line.
2023-01-11 17:22:39 -06:00
Nicolas Mowen
cb0c5c2587 Disable backchannel audio since it is not used (#5021)
* Disable backchannel audio since it is not used

* Don't need to block bachchannel audio for ffmpeg streams

* Formatting
2023-01-11 17:21:48 -06:00
Nicolas Mowen
1643b4d108 Clean up go2rtc logs to not show color text unicode (#5027)
* Use color logs for go2rtc

* Update docs to show need for formatted logs

* Fix log selector
2023-01-11 17:21:13 -06:00
James L
acd1fb9e3e System page: various minor UI tweaks (#4985)
* System page: various minor UI tweaks

* Be consistent with capitalisation

* Change detection start epoch to running/idle

* Remove detection start column entirely
2023-01-11 06:11:57 -06:00
Nicolas Mowen
ddde477770 Use scale_qsv and don't apply deinterlacing (#4997) 2023-01-11 06:11:26 -06:00
Nicolas Mowen
3edbb8dc41 Camera WebUI fixes (#5010)
* Show jsmpeg when restream is disabled

* Fix debug button status not showing correctly when switching cameras

* Change label to make clear only motion masks are shown
2023-01-11 06:09:58 -06:00
Nicolas Mowen
581c2591ae Use library to handle copying to clipboard (#4989)
* Use library to handle copying

* Typo
2023-01-10 06:23:04 -06:00
Nicolas Mowen
d49359e26a Fix None filter for sub labels (#4981) 2023-01-09 19:44:26 -06:00
Nicolas Mowen
e79eab711a Fix RPi preset (#4968) 2023-01-08 20:58:06 -06:00
Nicolas Mowen
5b7cd9ce64 Update go2rtc to rc7 (#4965) 2023-01-08 13:43:11 -07:00
Blake Blackshear
3cb96091ec update before install (#4966) 2023-01-08 14:39:39 -06:00
Nicolas Mowen
fdd2cc972e Update record.md (#4964) 2023-01-08 14:28:48 -06:00
Nicolas Mowen
61243ad34b Make error messages for gpu stats more clear (#4962)
* Make error messages for gpu stats more clear

* Make error messages for gpu stats more clear
2023-01-08 14:00:46 -06:00
Nicolas Mowen
5f4c439f57 Fix btbn pulling wrong build (#4961) 2023-01-08 09:23:58 -06:00
Blake Blackshear
1f963ec5aa Fix raspberry pi hwaccel (#4955)
* use ffmpeg from raspbian repo

* dynamically choose timeout param

* invert logic

* Add hwaccel presets for rpi

Co-authored-by: Nick Mowen <nickmowen213@gmail.com>
2023-01-08 07:04:58 -06:00
Pierre Belanger
bcbf0061ff Support for dynamic MQTT user configuration #4883 (#4956)
* Support for dynamic MQTT user configuration #4883

* Fix substitute condition & docs
2023-01-07 17:10:48 -06:00
Nicolas Mowen
57dce4ec38 Make label colors consistent (#4951) 2023-01-07 12:05:11 -06:00
20 changed files with 172 additions and 57 deletions

View File

@@ -27,7 +27,7 @@ RUN --mount=type=tmpfs,target=/tmp --mount=type=tmpfs,target=/var/cache/apt \
FROM wget AS go2rtc
ARG TARGETARCH
WORKDIR /rootfs/usr/local/go2rtc/bin
RUN wget -qO go2rtc "https://github.com/AlexxIT/go2rtc/releases/download/v0.1-rc.6/go2rtc_linux_${TARGETARCH}" \
RUN wget -qO go2rtc "https://github.com/AlexxIT/go2rtc/releases/download/v0.1-rc.7/go2rtc_linux_${TARGETARCH}" \
&& chmod +x go2rtc

View File

@@ -12,10 +12,7 @@ apt-get -qq install --no-install-recommends -y \
unzip locales tzdata libxml2 xz-utils \
python3-pip
# add raspberry pi repo
mkdir -p -m 600 /root/.gnupg
gpg --no-default-keyring --keyring /usr/share/keyrings/raspbian.gpg --keyserver keyserver.ubuntu.com --recv-keys 9165938D90FDDD2E
echo "deb [signed-by=/usr/share/keyrings/raspbian.gpg] http://raspbian.raspberrypi.org/raspbian/ bullseye main contrib non-free rpi" | tee /etc/apt/sources.list.d/raspi.list
# add coral repo
wget --quiet -O /usr/share/keyrings/google-edgetpu.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
@@ -30,21 +27,29 @@ apt-get -qq update
apt-get -qq install --no-install-recommends --no-install-suggests -y \
libedgetpu1-max python3-tflite-runtime python3-pycoral
# btbn-ffmpeg -> amd64 / arm64
if [[ "${TARGETARCH}" == "amd64" || "${TARGETARCH}" == "arm64" ]]; then
if [[ "${TARGETARCH}" == "amd64" ]]; then
btbn_arch="64"
else
btbn_arch="arm64"
fi
# btbn-ffmpeg -> amd64
if [[ "${TARGETARCH}" == "amd64" ]]; then
mkdir -p /usr/lib/btbn-ffmpeg
wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2022-07-31-12-37/ffmpeg-n5.1-2-g915ef932a3-linux${btbn_arch}-gpl-5.1.tar.xz"
wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2022-07-31-12-37/ffmpeg-n5.1-2-g915ef932a3-linux64-gpl-5.1.tar.xz"
tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/btbn-ffmpeg --strip-components 1
rm -rf btbn-ffmpeg.tar.xz /usr/lib/btbn-ffmpeg/doc /usr/lib/btbn-ffmpeg/bin/ffplay
fi
# ffmpeg -> arm32
if [[ "${TARGETARCH}" == "arm" ]]; then
# add raspberry pi repo
gpg --no-default-keyring --keyring /usr/share/keyrings/raspbian.gpg --keyserver keyserver.ubuntu.com --recv-keys 9165938D90FDDD2E
echo "deb [signed-by=/usr/share/keyrings/raspbian.gpg] http://raspbian.raspberrypi.org/raspbian/ bullseye main contrib non-free rpi" | tee /etc/apt/sources.list.d/raspi.list
apt-get -qq update
apt-get -qq install --no-install-recommends --no-install-suggests -y ffmpeg
fi
# ffmpeg -> arm64
if [[ "${TARGETARCH}" == "arm64" ]]; then
# add raspberry pi repo
gpg --no-default-keyring --keyring /usr/share/keyrings/raspbian.gpg --keyserver keyserver.ubuntu.com --recv-keys 82B129927FA3303E
echo "deb [signed-by=/usr/share/keyrings/raspbian.gpg] https://archive.raspberrypi.org/debian/ bullseye main" | tee /etc/apt/sources.list.d/raspi.list
apt-get -qq update
apt-get -qq install --no-install-recommends --no-install-suggests -y ffmpeg
fi

View File

@@ -1,3 +1,6 @@
log:
format: text
webrtc:
listen: ":8555"
candidates:

View File

@@ -52,6 +52,8 @@ mqtt:
# NOTE: must be unique if you are running multiple instances
client_id: frigate
# Optional: user
# NOTE: MQTT user can be specified with an environment variables that must begin with 'FRIGATE_'.
# e.g. user: '{FRIGATE_MQTT_USER}'
user: mqtt_user
# Optional: password
# NOTE: MQTT password can be specified with an environment variables that must begin with 'FRIGATE_'.

View File

@@ -24,6 +24,9 @@ webRTC works by creating a websocket connection on extra ports. One of the follo
* For local webRTC, you will need to create your own go2rtc config:
```yaml
log:
format: text
webrtc:
listen: ":8555"
candidates:

View File

@@ -3,9 +3,11 @@ id: record
title: Recording
---
Recordings can be enabled and are stored at `/media/frigate/recordings`. The folder structure for the recordings is `YYYY-MM/DD/HH/<camera_name>/MM.SS.mp4`. These recordings are written directly from your camera stream without re-encoding. Each camera supports a configurable retention policy in the config. Frigate chooses the largest matching retention value between the recording retention and the event retention when determining if a recording should be removed.
Recordings can be enabled and are stored at `/media/frigate/recordings`. The folder structure for the recordings is `YYYY-MM-DD/HH/<camera_name>/MM.SS.mp4`. These recordings are written directly from your camera stream without re-encoding. Each camera supports a configurable retention policy in the config. Frigate chooses the largest matching retention value between the recording retention and the event retention when determining if a recording should be removed.
H265 recordings can be viewed in Edge and Safari only. All other browsers require recordings to be encoded with H264.
New recording segments are written from the camera stream to cache, they are only moved to disk if they match the setup recording retention policy.
H265 recordings can be viewed in Chrome 108+, Edge and Safari only. All other browsers require recordings to be encoded with H264.
## Will Frigate delete old recordings if my storage runs out?

View File

@@ -166,6 +166,7 @@ Events from the database. Accepts the following query string parameters:
| `has_snapshot` | int | Filter to events that have snapshots (0 or 1) |
| `has_clip` | int | Filter to events that have clips (0 or 1) |
| `include_thumbnails` | int | Include thumbnails in the response (0 or 1) |
| `in_progress` | int | Limit to events in progress (0 or 1) |
### `GET /api/events/summary`

View File

@@ -863,8 +863,9 @@ class FrigateConfig(FrigateBaseModel):
"""Merge camera config with globals."""
config = self.copy(deep=True)
# MQTT password substitution
if config.mqtt.password:
# MQTT user/password substitutions
if config.mqtt.user or config.mqtt.password:
config.mqtt.user = config.mqtt.user.format(**FRIGATE_ENV_VARS)
config.mqtt.password = config.mqtt.password.format(**FRIGATE_ENV_VARS)
# Global config to propagate down to camera level
@@ -968,7 +969,7 @@ class FrigateConfig(FrigateBaseModel):
for _, camera in config.cameras.items():
enabled_labels.update(camera.objects.track)
config.model.create_colormap(enabled_labels)
config.model.create_colormap(sorted(enabled_labels))
for key, detector in config.detectors.items():
detector_config: DetectorConfig = parse_obj_as(DetectorConfig, detector)

View File

@@ -7,6 +7,7 @@ YAML_EXT = (".yaml", ".yml")
PLUS_ENV_VAR = "PLUS_API_KEY"
PLUS_API_HOST = "https://api.frigate.video"
MAX_SEGMENT_DURATION = 600
BTBN_PATH = "/usr/lib/btbn-ffmpeg"
# Regex Consts

View File

@@ -1,8 +1,13 @@
"""Handles inserting and maintaining ffmpeg presets."""
import os
from typing import Any
from frigate.version import VERSION
from frigate.const import BTBN_PATH
TIMEOUT_PARAM = "-timeout" if os.path.exists(BTBN_PATH) else "-stimeout"
_user_agent_args = [
"-user_agent",
@@ -75,6 +80,8 @@ PRESETS_HW_ACCEL_DECODE = {
}
PRESETS_HW_ACCEL_SCALE = {
"preset-rpi-32-h264": ["-f", "rawvideo", "-pix_fmt", "yuv420p"],
"preset-rpi-64-h264": ["-f", "rawvideo", "-pix_fmt", "yuv420p"],
"preset-vaapi": [
"-vf",
"fps={},scale_vaapi=w={}:h={},hwdownload,format=yuv420p",
@@ -83,13 +90,13 @@ PRESETS_HW_ACCEL_SCALE = {
],
"preset-intel-qsv-h264": [
"-vf",
"vpp_qsv=framerate={}:scale_mode=1:w={}:h={}:detail=50:denoise=100:deinterlace=2:format=nv12,hwdownload,format=nv12,format=yuv420p",
"vpp_qsv=framerate={},scale_qsv=w={}:h={}:format=nv12,hwdownload,format=nv12,format=yuv420p",
"-f",
"rawvideo",
],
"preset-intel-qsv-h265": [
"-vf",
"vpp_qsv=framerate={}:scale_mode=1:w={}:h={}:detail=50:denoise=100:deinterlace=2:format=nv12,hwdownload,format=nv12,format=yuv420p",
"vpp_qsv=framerate={},scale_qsv=w={}:h={}:format=nv12,hwdownload,format=nv12,format=yuv420p",
"-f",
"rawvideo",
],
@@ -114,6 +121,8 @@ PRESETS_HW_ACCEL_SCALE = {
}
PRESETS_HW_ACCEL_ENCODE = {
"preset-rpi-32-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m -g 50 -bf 0 {1}",
"preset-rpi-64-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m -g 50 -bf 0 {1}",
"preset-intel-qsv-h264": "ffmpeg -hide_banner {0} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {1}",
"preset-intel-qsv-h265": "ffmpeg -hide_banner {0} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {1}",
"preset-nvidia-h264": "ffmpeg -hide_banner {0} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {1}",
@@ -122,6 +131,8 @@ PRESETS_HW_ACCEL_ENCODE = {
}
PRESETS_HW_ACCEL_GO2RTC_ENGINE = {
"preset-rpi-32-h264": "v4l2m2m",
"preset-rpi-64-h264": "v4l2m2m",
"preset-intel-vaapi": "vaapi",
"preset-intel-qsv-h264": "vaapi", # go2rtc doesn't support qsv
"preset-intel-qsv-h265": "vaapi",
@@ -258,7 +269,7 @@ PRESETS_INPUT = {
"+genpts+discardcorrupt",
"-rtsp_transport",
"tcp",
"-timeout",
TIMEOUT_PARAM,
"5000000",
"-use_wallclock_as_timestamps",
"1",
@@ -271,7 +282,7 @@ PRESETS_INPUT = {
"+genpts+discardcorrupt",
"-rtsp_transport",
"udp",
"-timeout",
TIMEOUT_PARAM,
"5000000",
"-use_wallclock_as_timestamps",
"1",
@@ -290,7 +301,7 @@ PRESETS_INPUT = {
"+genpts+discardcorrupt",
"-rtsp_transport",
"tcp",
"-timeout",
TIMEOUT_PARAM,
"5000000",
"-use_wallclock_as_timestamps",
"1",

View File

@@ -565,6 +565,7 @@ def events():
before = request.args.get("before", type=float)
has_clip = request.args.get("has_clip", type=int)
has_snapshot = request.args.get("has_snapshot", type=int)
in_progress = request.args.get("in_progress", type=int)
include_thumbnails = request.args.get("include_thumbnails", default=1, type=int)
favorites = request.args.get("favorites", type=int)
@@ -602,8 +603,13 @@ def events():
# for example a sub label 'bob' would get events
# with sub labels 'bob' and 'bob, john'
sub_label_clauses = []
filtered_sub_labels = sub_labels.split(",")
for label in sub_labels.split(","):
if "None" in filtered_sub_labels:
filtered_sub_labels.remove("None")
sub_label_clauses.append((Event.sub_label.is_null()))
for label in filtered_sub_labels:
sub_label_clauses.append((Event.sub_label.cast("text") % f"*{label}*"))
sub_label_clause = reduce(operator.or_, sub_label_clauses)
@@ -613,8 +619,13 @@ def events():
# use matching so events with multiple zones
# still match on a search where any zone matches
zone_clauses = []
filtered_zones = zones.split(",")
for zone in zones.split(","):
if "None" in filtered_zones:
filtered_zones.remove("None")
zone_clauses.append((Event.zones.length() == 0))
for zone in filtered_zones:
zone_clauses.append((Event.zones.cast("text") % f'*"{zone}"*'))
zone_clause = reduce(operator.or_, zone_clauses)
@@ -632,6 +643,9 @@ def events():
if not has_snapshot is None:
clauses.append((Event.has_snapshot == has_snapshot))
if not in_progress is None:
clauses.append((Event.end_time.is_null(in_progress)))
if not include_thumbnails:
excluded_fields.append(Event.thumbnail)
else:

View File

@@ -278,9 +278,7 @@ class RecordingMaintainer(threading.Thread):
directory = os.path.join(
RECORD_DIR,
start_time.replace(tzinfo=datetime.timezone.utc)
.astimezone(tz=None)
.strftime("%Y-%m-%d/%H"),
start_time.astimezone(tz=datetime.timezone.utc).strftime("%Y-%m-%d/%H"),
camera,
)

View File

@@ -52,7 +52,9 @@ class RestreamApi:
input.path.startswith("rtsp")
and not camera.restream.force_audio
):
self.relays[cam_name] = escape_special_characters(input.path)
self.relays[
cam_name
] = f"{escape_special_characters(input.path)}#backchannel=0"
else:
# go2rtc only supports rtsp for direct relay, otherwise ffmpeg is used
self.relays[cam_name] = get_manual_go2rtc_stream(

View File

@@ -738,12 +738,69 @@ def escape_special_characters(path: str) -> str:
return path
def get_cgroups_version() -> str:
"""Determine what version of cgroups is enabled"""
stat_command = ["stat", "-fc", "%T", "/sys/fs/cgroup"]
p = sp.run(
stat_command,
encoding="ascii",
capture_output=True,
)
if p.returncode == 0:
value: str = p.stdout.strip().lower()
if value == "cgroup2fs":
return "cgroup2"
elif value == "tmpfs":
return "cgroup"
else:
logger.debug(
f"Could not determine cgroups version: unhandled filesystem {value}"
)
else:
logger.debug(f"Could not determine cgroups version: {p.stderr}")
return "unknown"
def get_docker_memlimit_bytes() -> int:
"""Get mem limit in bytes set in docker if present. Returns -1 if no limit detected"""
# check running a supported cgroups version
if get_cgroups_version() == "cgroup2":
memlimit_command = ["cat", "/sys/fs/cgroup/memory.max"]
p = sp.run(
memlimit_command,
encoding="ascii",
capture_output=True,
)
if p.returncode == 0:
value: str = p.stdout.strip()
if value.isnumeric():
return int(value)
elif value.lower() == "max":
return -1
else:
logger.debug(f"Unable to get docker memlimit: {p.stderr}")
return -1
def get_cpu_stats() -> dict[str, dict]:
"""Get cpu usages for each process id"""
usages = {}
# -n=2 runs to ensure extraneous values are not included
top_command = ["top", "-b", "-n", "2"]
docker_memlimit = get_docker_memlimit_bytes() / 1024
p = sp.run(
top_command,
encoding="ascii",
@@ -759,9 +816,18 @@ def get_cpu_stats() -> dict[str, dict]:
for line in lines:
stats = list(filter(lambda a: a != "", line.strip().split(" ")))
try:
if docker_memlimit > 0:
mem_res = int(stats[5])
mem_pct = str(
round((float(mem_res) / float(docker_memlimit)) * 100, 1)
)
else:
mem_pct = stats[9]
usages[stats[0]] = {
"cpu": stats[8],
"mem": stats[9],
"mem": mem_pct,
}
except:
continue
@@ -780,7 +846,7 @@ def get_amd_gpu_stats() -> dict[str, str]:
)
if p.returncode != 0:
logger.error(p.stderr)
logger.error(f"Unable to poll radeon GPU stats: {p.stderr}")
return None
else:
usages = p.stdout.split(",")
@@ -816,7 +882,7 @@ def get_intel_gpu_stats() -> dict[str, str]:
# timeout has a non-zero returncode when timeout is reached
if p.returncode != 124:
logger.error(p.stderr)
logger.error(f"Unable to poll intel GPU stats: {p.stderr}")
return None
else:
reading = "".join(p.stdout.split())
@@ -866,7 +932,7 @@ def get_nvidia_gpu_stats() -> dict[str, str]:
)
if p.returncode != 0:
logger.error(p.stderr)
logger.error(f"Unable to poll nvidia GPU stats: {p.stderr}")
return None
else:
usages = p.stdout.split("\n")[1].strip().split(",")

View File

@@ -14,6 +14,7 @@
"dependencies": {
"@cycjimmy/jsmpeg-player": "^6.0.5",
"axios": "^1.2.2",
"copy-to-clipboard": "3.3.3",
"date-fns": "^2.29.3",
"idb-keyval": "^6.2.0",
"immer": "^9.0.16",

View File

@@ -3,13 +3,14 @@ import { useCallback, useState } from 'preact/hooks';
export default function ButtonsTabbed({
viewModes = [''],
currentViewMode = '',
setViewMode = null,
setHeader = null,
headers = [''],
className = 'text-gray-600 py-0 px-4 block hover:text-gray-500',
selectedClassName = `${className} focus:outline-none border-b-2 font-medium border-gray-500`,
}) {
const [selected, setSelected] = useState(0);
const [selected, setSelected] = useState(viewModes ? viewModes.indexOf(currentViewMode) : 0);
const captitalize = (str) => {
return `${str.charAt(0).toUpperCase()}${str.slice(1)}`;
};

View File

@@ -30,7 +30,7 @@ export default function Camera({ camera }) {
? Math.round(cameraConfig.restream.jsmpeg.height * (cameraConfig.detect.width / cameraConfig.detect.height))
: 0;
const [viewSource, setViewSource, sourceIsLoaded] = usePersistence(`${camera}-source`, 'mse');
const sourceValues = cameraConfig && cameraConfig.restream.enabled ? ['mse', 'webrtc', 'jsmpeg'] : ['mse'];
const sourceValues = cameraConfig && cameraConfig.restream.enabled ? ['mse', 'webrtc', 'jsmpeg'] : ['jsmpeg'];
const [options, setOptions] = usePersistence(`${camera}-feed`, emptyObject);
const handleSetOption = useCallback(
@@ -77,7 +77,7 @@ export default function Camera({ camera }) {
labelPosition="after"
/>
<Switch checked={options['zones']} id="zones" onChange={handleSetOption} label="Zones" labelPosition="after" />
<Switch checked={options['mask']} id="mask" onChange={handleSetOption} label="Masks" labelPosition="after" />
<Switch checked={options['mask']} id="mask" onChange={handleSetOption} label="Motion Masks" labelPosition="after" />
<Switch
checked={options['motion']}
id="motion"
@@ -98,7 +98,7 @@ export default function Camera({ camera }) {
let player;
if (viewMode === 'live') {
if (viewSource == 'mse') {
if (viewSource == 'mse' && cameraConfig.restream.enabled) {
if (videojs.browser.IS_IOS) {
player = (
<Fragment>
@@ -116,7 +116,7 @@ export default function Camera({ camera }) {
</Fragment>
);
}
} else if (viewSource == 'webrtc') {
} else if (viewSource == 'webrtc' && cameraConfig.restream.enabled) {
player = (
<Fragment>
<div className="max-w-5xl">
@@ -170,7 +170,7 @@ export default function Camera({ camera }) {
</select>
</div>
<ButtonsTabbed viewModes={['live', 'debug']} setViewMode={setViewMode} />
<ButtonsTabbed viewModes={['live', 'debug']} currentViewMode={viewMode} setViewMode={setViewMode} />
{player}

View File

@@ -8,6 +8,7 @@ import { useEffect, useState } from 'preact/hooks';
import Button from '../components/Button';
import { editor, Uri } from 'monaco-editor';
import { setDiagnosticsOptions } from 'monaco-yaml';
import copy from 'copy-to-clipboard';
export default function Config() {
const apiHost = useApiHost();
@@ -40,7 +41,7 @@ export default function Config() {
};
const handleCopyConfig = async () => {
await window.navigator.clipboard.writeText(window.editor.getValue());
copy(window.editor.getValue());
};
useEffect(() => {

View File

@@ -38,7 +38,7 @@ export default function Logs() {
<div className="space-y-4 p-2 px-4">
<Heading>Logs</Heading>
<ButtonsTabbed viewModes={['frigate', 'go2rtc', 'nginx']} setViewMode={setLogService} />
<ButtonsTabbed viewModes={['frigate', 'go2rtc', 'nginx']} currentViewMode={logService} setViewMode={setLogService} />
<Button className="" onClick={handleCopyLogs}>
Copy to Clipboard

View File

@@ -9,6 +9,7 @@ import axios from 'axios';
import { Table, Tbody, Thead, Tr, Th, Td } from '../components/Table';
import { useState } from 'preact/hooks';
import Dialog from '../components/Dialog';
import copy from 'copy-to-clipboard';
const emptyObject = Object.freeze({});
@@ -54,7 +55,7 @@ export default function System() {
};
const onCopyFfprobe = async () => {
await window.navigator.clipboard.writeText(JSON.stringify(state.ffprobe, null, 2));
copy(JSON.stringify(state.ffprobe, null, 2));
setState({ ...state, ffprobe: '', showFfprobe: false });
};
@@ -73,7 +74,7 @@ export default function System() {
};
const onCopyVainfo = async () => {
await window.navigator.clipboard.writeText(JSON.stringify(state.vaifp, null, 2));
copy(JSON.stringify(state.vainfo, null, 2));
setState({ ...state, vainfo: '', showVainfo: false });
};
@@ -141,15 +142,17 @@ export default function System() {
<Thead>
<Tr>
<Th>P-ID</Th>
<Th>Detection Start</Th>
<Th>Inference Speed</Th>
<Th>CPU %</Th>
<Th>Memory %</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>{detectors[detector]['pid']}</Td>
<Td>{detectors[detector]['detection_start']}</Td>
<Td>{detectors[detector]['inference_speed']}</Td>
<Td>{detectors[detector]['inference_speed']} ms</Td>
<Td>{cpu_usages[detectors[detector]['pid']]?.['cpu'] || '- '}%</Td>
<Td>{cpu_usages[detectors[detector]['pid']]?.['mem'] || '- '}%</Td>
</Tr>
</Tbody>
</Table>
@@ -184,7 +187,7 @@ export default function System() {
<Table className="w-full">
<Thead>
<Tr>
<Th>Gpu %</Th>
<Th>GPU %</Th>
<Th>Memory %</Th>
</Tr>
</Thead>
@@ -219,20 +222,27 @@ export default function System() {
<Tr>
<Th>Process</Th>
<Th>P-ID</Th>
<Th>fps</Th>
<Th>Cpu %</Th>
<Th>FPS</Th>
<Th>CPU %</Th>
<Th>Memory %</Th>
</Tr>
</Thead>
<Tbody>
<Tr key="capture" index="0">
<Tr key="ffmpeg" index="0">
<Td>ffmpeg</Td>
<Td>{cameras[camera]['ffmpeg_pid'] || '- '}</Td>
<Td>{cameras[camera]['camera_fps'] || '- '}</Td>
<Td>{cpu_usages[cameras[camera]['ffmpeg_pid']]?.['cpu'] || '- '}%</Td>
<Td>{cpu_usages[cameras[camera]['ffmpeg_pid']]?.['mem'] || '- '}%</Td>
</Tr>
<Tr key="capture" index="1">
<Td>Capture</Td>
<Td>{cameras[camera]['capture_pid'] || '- '}</Td>
<Td>{cameras[camera]['process_fps'] || '- '}</Td>
<Td>{cpu_usages[cameras[camera]['capture_pid']]?.['cpu'] || '- '}%</Td>
<Td>{cpu_usages[cameras[camera]['capture_pid']]?.['mem'] || '- '}%</Td>
</Tr>
<Tr key="detect" index="1">
<Tr key="detect" index="2">
<Td>Detect</Td>
<Td>{cameras[camera]['pid'] || '- '}</Td>
<Td>
@@ -241,13 +251,6 @@ export default function System() {
<Td>{cpu_usages[cameras[camera]['pid']]?.['cpu'] || '- '}%</Td>
<Td>{cpu_usages[cameras[camera]['pid']]?.['mem'] || '- '}%</Td>
</Tr>
<Tr key="ffmpeg" index="2">
<Td>ffmpeg</Td>
<Td>{cameras[camera]['ffmpeg_pid'] || '- '}</Td>
<Td>{cameras[camera]['camera_fps'] || '- '}</Td>
<Td>{cpu_usages[cameras[camera]['ffmpeg_pid']]?.['cpu'] || '- '}%</Td>
<Td>{cpu_usages[cameras[camera]['ffmpeg_pid']]?.['mem'] || '- '}%</Td>
</Tr>
</Tbody>
</Table>
</div>