mirror of
https://github.com/kerberos-io/agent.git
synced 2026-09-03 16:58:34 +00:00
Compare commits
229 Commits
v1.0.0-bet
...
d4e1008
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4e10083c4 | ||
|
|
898a11868e | ||
|
|
a098052ee8 | ||
|
|
6f79086a27 | ||
|
|
6ee07d4c12 | ||
|
|
a69869440f | ||
|
|
dcd4e08c6f | ||
|
|
22e7a0fec9 | ||
|
|
8daebd434d | ||
|
|
8381cd1a5c | ||
|
|
051299f331 | ||
|
|
20f2c764fe | ||
|
|
07619c2bce | ||
|
|
fb86efe715 | ||
|
|
4eafe6db84 | ||
|
|
797f986d5d | ||
|
|
864a8ecffa | ||
|
|
7e48be06e3 | ||
|
|
27861aa0a5 | ||
|
|
6d45cb7e6d | ||
|
|
1964b893a5 | ||
|
|
e30c8fe39c | ||
|
|
b183561dc6 | ||
|
|
caa4952e99 | ||
|
|
5e15b9ac75 | ||
|
|
4fadd770f8 | ||
|
|
7ac2f71de1 | ||
|
|
b49eef2d1c | ||
|
|
b8735aba01 | ||
|
|
c73584a743 | ||
|
|
ef4fc0a184 | ||
|
|
8cada20b1e | ||
|
|
ba4ac215b5 | ||
|
|
947ff473a0 | ||
|
|
14dc1a2fa3 | ||
|
|
df69cdc6a4 | ||
|
|
dd54e08e10 | ||
|
|
7e3bb2359e | ||
|
|
5d00053b6f | ||
|
|
d573c2b829 | ||
|
|
eda9eb90d8 | ||
|
|
34849e8250 | ||
|
|
d069a606d7 | ||
|
|
f0b042a2d1 | ||
|
|
8a3ec65ec1 | ||
|
|
e53715b9b0 | ||
|
|
9ef1339f8f | ||
|
|
afaa51aac3 | ||
|
|
fb803df15e | ||
|
|
de0c4fa657 | ||
|
|
18484f407c | ||
|
|
f4a7bc5650 | ||
|
|
9410289b4f | ||
|
|
8eecae6f9a | ||
|
|
63cf88b88d | ||
|
|
cb9403cbf9 | ||
|
|
27da1dbc74 | ||
|
|
98d9185c20 | ||
|
|
dec367a8f5 | ||
|
|
8a3c1e8f08 | ||
|
|
89c385ae2f | ||
|
|
e920cb2a6b | ||
|
|
3f6204e2eb | ||
|
|
ae9d42e821 | ||
|
|
31400bd5e7 | ||
|
|
7973559502 | ||
|
|
d4bfc8677a | ||
|
|
155f1c7cdf | ||
|
|
9643a79603 | ||
|
|
3cc67b485e | ||
|
|
2175d76f94 | ||
|
|
2a069f8881 | ||
|
|
c46765ee8f | ||
|
|
427fad262c | ||
|
|
a895b24425 | ||
|
|
659ba3e67d | ||
|
|
7e394e1c93 | ||
|
|
886522039a | ||
|
|
155f99597a | ||
|
|
d67ac25e87 | ||
|
|
1fdfddeb43 | ||
|
|
770192ac57 | ||
|
|
228cfd844a | ||
|
|
4fbcfbc99d | ||
|
|
bfb72e8268 | ||
|
|
e375c107ef | ||
|
|
99ce644e5d | ||
|
|
05c2d4c583 | ||
|
|
2265eb88e7 | ||
|
|
926e392076 | ||
|
|
7c0cbd4771 | ||
|
|
70d27dc502 | ||
|
|
fece388ae9 | ||
|
|
5543be51da | ||
|
|
cf28d69cce | ||
|
|
9ea4a96165 | ||
|
|
7bd4934ee7 | ||
|
|
fb5919af99 | ||
|
|
8cf8a2e253 | ||
|
|
8dfd15bc3a | ||
|
|
05fab6c7f7 | ||
|
|
9ec52d445d | ||
|
|
a68aaaa214 | ||
|
|
97d2f8fb26 | ||
|
|
b2bc10865c | ||
|
|
7b6ac61a71 | ||
|
|
27b2842481 | ||
|
|
8507e84ea0 | ||
|
|
66df22709f | ||
|
|
c8c9a0fd6e | ||
|
|
f6b95ce7f3 | ||
|
|
6b31ddcbfc | ||
|
|
73892ee86f | ||
|
|
1a0458de11 | ||
|
|
42647172b9 | ||
|
|
e29577e1c5 | ||
|
|
9012bf2bf0 | ||
|
|
0fa1943701 | ||
|
|
a0c1acfd85 | ||
|
|
6413cc82c7 | ||
|
|
e57908c9b4 | ||
|
|
c36007ab27 | ||
|
|
928604e12b | ||
|
|
e33ee6cfa6 | ||
|
|
517b4b26f2 | ||
|
|
06b3d7e67f | ||
|
|
508422d461 | ||
|
|
528638f69c | ||
|
|
70e7507a15 | ||
|
|
df019cf347 | ||
|
|
e88a786116 | ||
|
|
7242212dd0 | ||
|
|
e3dd5fd34b | ||
|
|
2b90bdbc0b | ||
|
|
cd4e9eed3d | ||
|
|
0658f9afbb | ||
|
|
541630a65f | ||
|
|
4e46f7f4bd | ||
|
|
4da3c7a50c | ||
|
|
47fb5b81ab | ||
|
|
b27b2f011b | ||
|
|
40ea660609 | ||
|
|
d70314d4ab | ||
|
|
8867c1dde5 | ||
|
|
a36d53a89b | ||
|
|
f634b98552 | ||
|
|
3942eb5ec1 | ||
|
|
f45413dab9 | ||
|
|
5795a41f92 | ||
|
|
ddc6491058 | ||
|
|
9c940e18d7 | ||
|
|
80b12e229a | ||
|
|
b9a81a31c9 | ||
|
|
1bc063fdbb | ||
|
|
98b897e4fe | ||
|
|
312e501da4 | ||
|
|
0d2e68af3f | ||
|
|
e16d933e56 | ||
|
|
8ee5b42fde | ||
|
|
05447abe93 | ||
|
|
6c91f233e3 | ||
|
|
feaba1afb5 | ||
|
|
e70a66f7b4 | ||
|
|
a78ccd023c | ||
|
|
87ac0932c3 | ||
|
|
8e1dcd7002 | ||
|
|
1b96d01964 | ||
|
|
c013308afe | ||
|
|
e28d133ef0 | ||
|
|
e8f03cd7d7 | ||
|
|
31f7eb5a9a | ||
|
|
89c39aa853 | ||
|
|
f349926c77 | ||
|
|
bf0ae4cb64 | ||
|
|
fb666bb622 | ||
|
|
b69b52d292 | ||
|
|
d11180f26e | ||
|
|
69461c8c7a | ||
|
|
8a373adc2c | ||
|
|
eb3355da9a | ||
|
|
2cda3ad929 | ||
|
|
7a344a987e | ||
|
|
3ce9adc95c | ||
|
|
e5c71bb6b2 | ||
|
|
a021a9e332 | ||
|
|
d3bd8031a7 | ||
|
|
bb18fff7d9 | ||
|
|
9ed2873adb | ||
|
|
f891ac375b | ||
|
|
630b34ca27 | ||
|
|
ecc7514d2b | ||
|
|
9e395bb8a7 | ||
|
|
85fe51f0f7 | ||
|
|
cb247e0312 | ||
|
|
9f933ac9ed | ||
|
|
b0455c6bda | ||
|
|
bac247f15e | ||
|
|
50823c8f8a | ||
|
|
2b81efb2c5 | ||
|
|
013e93be67 | ||
|
|
934ea927b2 | ||
|
|
e54a4097a1 | ||
|
|
bc98b87dc2 | ||
|
|
1c7883b76a | ||
|
|
3850a91737 | ||
|
|
6e8599efda | ||
|
|
1f66eceb3d | ||
|
|
5b551d128f | ||
|
|
77373dfd1d | ||
|
|
c4fdf7de01 | ||
|
|
89875358b1 | ||
|
|
70bc2a3af1 | ||
|
|
b228d808db | ||
|
|
7fe3f5fb0f | ||
|
|
4daf48a39c | ||
|
|
20a1802bdd | ||
|
|
b812b49e11 | ||
|
|
30fb0b675b | ||
|
|
e6257f4ec6 | ||
|
|
38b28b965c | ||
|
|
16e4c68fb3 | ||
|
|
784fe73a55 | ||
|
|
3a83ffc7b1 | ||
|
|
b6ef1249b3 | ||
|
|
c8a863a88d | ||
|
|
da1f7ab574 | ||
|
|
6938ecfca2 | ||
|
|
9461fe8874 | ||
|
|
a770825648 |
@@ -5,11 +5,11 @@ version: 2
|
||||
jobs:
|
||||
machinery:
|
||||
docker:
|
||||
- image: kerberos/base:6e68480
|
||||
- image: kerberos/base:91ab4d4
|
||||
working_directory: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}}
|
||||
steps:
|
||||
- checkout
|
||||
- run: apt-get install -y --no-install-recommends libavcodec-dev libavformat-dev libswscale-dev
|
||||
- run: apt-get update && apt-get install -y --no-install-recommends libavcodec-dev libavformat-dev libswscale-dev libjpeg62-turbo-dev
|
||||
- run: cd machinery && go mod download
|
||||
- run: cd machinery && go test -v ./...
|
||||
- run: cd machinery && go vet
|
||||
|
||||
2
.devcontainer/Dockerfile
Normal file
2
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM kerberos/devcontainer:9da0ee3
|
||||
LABEL AUTHOR=Kerberos.io
|
||||
33
.devcontainer/devcontainer.json
Normal file
33
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,33 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/docker-existing-dockerfile
|
||||
{
|
||||
"name": "A Dockerfile containing FFmpeg, OpenCV, Go and Yarn",
|
||||
// Sets the run context to one level up instead of the .devcontainer folder.
|
||||
"context": "..",
|
||||
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
|
||||
"dockerFile": "./Dockerfile",
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [
|
||||
3000,
|
||||
80
|
||||
],
|
||||
// Uncomment the next line to run commands after the container is created - for example installing curl.
|
||||
"postCreateCommand": "cd ui && yarn install && yarn build && cd ../machinery && go mod download",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-contrib/features/ansible:1": {},
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-kubernetes-tools.vscode-kubernetes-tools",
|
||||
"GitHub.copilot"
|
||||
]
|
||||
}
|
||||
},
|
||||
// Uncomment when using a ptrace-based debugger like C++, Go, and Rust
|
||||
// "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
|
||||
// Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker.
|
||||
// "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ],
|
||||
// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
|
||||
// "remoteUser": "vscode"
|
||||
}
|
||||
4
.github/workflows/docker-dev.yml
vendored
4
.github/workflows/docker-dev.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Docker Development build
|
||||
name: Docker development build
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
architecture: [arm64, arm/v7]
|
||||
architecture: [arm64, arm/v7, arm/v6]
|
||||
steps:
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
|
||||
4
.github/workflows/docker-nightly.yml
vendored
4
.github/workflows/docker-nightly.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Docker Nightly build
|
||||
name: Docker nightly build
|
||||
|
||||
on:
|
||||
# Triggers the workflow every day at 9PM (CET).
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
architecture: [arm64, arm/v7]
|
||||
architecture: [arm64, arm/v7, arm/v6]
|
||||
steps:
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
|
||||
92
.github/workflows/docker.yml
vendored
92
.github/workflows/docker.yml
vendored
@@ -1,40 +1,67 @@
|
||||
name: Docker Production build
|
||||
name: Docker master build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
REPO: kerberos/agent
|
||||
|
||||
jobs:
|
||||
build-amd64:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
architecture: [amd64]
|
||||
steps:
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Available platforms
|
||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||
- name: Run Buildx
|
||||
run: docker buildx build --platform linux/${{matrix.architecture}} -t kerberos/agent:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7) --push .
|
||||
- name: Create new and append to manifest
|
||||
run: docker buildx imagetools create -t kerberos/agent:$(echo $GITHUB_SHA | cut -c1-7) kerberos/agent:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7)
|
||||
- name: Create new and append to manifest latest
|
||||
run: docker buildx imagetools create -t kerberos/agent:latest kerberos/agent:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7)
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- uses: benjlevesque/short-sha@v2.1
|
||||
id: short-sha
|
||||
with:
|
||||
length: 7
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Available platforms
|
||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||
- name: Run Buildx
|
||||
run: docker buildx build --platform linux/$(echo ${{matrix.architecture}} | tr - /) -t $REPO-arch:arch-${{matrix.architecture}}-${{steps.short-sha.outputs.sha}} --push .
|
||||
- name: Create new and append to manifest
|
||||
run: docker buildx imagetools create -t $REPO:${{ steps.short-sha.outputs.sha }} $REPO-arch:arch-${{matrix.architecture}}-${{steps.short-sha.outputs.sha}}
|
||||
- name: Create new and append to manifest latest
|
||||
run: docker buildx imagetools create -t $REPO:latest $REPO-arch:arch-${{matrix.architecture}}-${{steps.short-sha.outputs.sha}}
|
||||
- name: Run Buildx with output
|
||||
run: docker buildx build --platform linux/$(echo ${{matrix.architecture}} | tr - /) -t $REPO-arch:arch-$(echo ${{matrix.architecture}} | tr / -)-${{steps.short-sha.outputs.sha}} --output type=tar,dest=output-${{matrix.architecture}}.tar .
|
||||
- name: Strip binary
|
||||
run: mkdir -p output/ && tar -xf output-${{matrix.architecture}}.tar -C output && rm output-${{matrix.architecture}}.tar && cd output/ && tar -cf ../output-${{matrix.architecture}}.tar -C home/agent . && rm -rf output
|
||||
- uses: rickstaa/action-create-tag@v1
|
||||
with:
|
||||
tag: ${{ steps.short-sha.outputs.sha }}
|
||||
message: "Release ${{ steps.short-sha.outputs.sha }}"
|
||||
- name: Create a release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
latest: true
|
||||
name: ${{ steps.short-sha.outputs.sha }}
|
||||
tag: ${{ steps.short-sha.outputs.sha }}
|
||||
artifacts: "output-${{matrix.architecture}}.tar"
|
||||
build-other:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
needs: build-amd64
|
||||
strategy:
|
||||
matrix:
|
||||
architecture: [arm64, arm/v7]
|
||||
architecture: [arm64, arm-v7, arm-v6]
|
||||
steps:
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
@@ -43,6 +70,10 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- uses: benjlevesque/short-sha@v2.1
|
||||
id: short-sha
|
||||
with:
|
||||
length: 7
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
@@ -50,8 +81,21 @@ jobs:
|
||||
- name: Available platforms
|
||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||
- name: Run Buildx
|
||||
run: docker buildx build --platform linux/${{matrix.architecture}} -t kerberos/agent:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7) --push .
|
||||
run: docker buildx build --platform linux/$(echo ${{matrix.architecture}} | tr - /) -t $REPO-arch:arch-${{matrix.architecture}}-${{steps.short-sha.outputs.sha}} --push .
|
||||
- name: Create new and append to manifest
|
||||
run: docker buildx imagetools create --append -t kerberos/agent:$(echo $GITHUB_SHA | cut -c1-7) kerberos/agent:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7)
|
||||
run: docker buildx imagetools create --append -t $REPO:${{ steps.short-sha.outputs.sha }} $REPO-arch:arch-${{matrix.architecture}}-${{steps.short-sha.outputs.sha}}
|
||||
- name: Create new and append to manifest latest
|
||||
run: docker buildx imagetools create --append -t kerberos/agent:latest kerberos/agent:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7)
|
||||
run: docker buildx imagetools create --append -t $REPO:latest $REPO-arch:arch-${{matrix.architecture}}-${{steps.short-sha.outputs.sha}}
|
||||
- name: Run Buildx with output
|
||||
run: docker buildx build --platform linux/$(echo ${{matrix.architecture}} | tr - /) -t $REPO-arch:arch-$(echo ${{matrix.architecture}} | tr / -)-${{steps.short-sha.outputs.sha}} --output type=tar,dest=output-${{matrix.architecture}}.tar .
|
||||
- name: Strip binary
|
||||
run: mkdir -p output/ && tar -xf output-${{matrix.architecture}}.tar -C output && rm output-${{matrix.architecture}}.tar && cd output/ && tar -cf ../output-${{matrix.architecture}}.tar -C home/agent . && rm -rf output
|
||||
- name: Create a release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
latest: true
|
||||
allowUpdates: true
|
||||
name: ${{ steps.short-sha.outputs.sha }}
|
||||
tag: ${{ steps.short-sha.outputs.sha }}
|
||||
artifacts: "output-${{matrix.architecture}}.tar"
|
||||
|
||||
9
.github/workflows/go.yml
vendored
9
.github/workflows/go.yml
vendored
@@ -12,11 +12,12 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: kerberos/base:6e68480
|
||||
image: kerberos/base:70d69dc
|
||||
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.17, 1.18]
|
||||
go-version: [1.17, 1.18, 1.19]
|
||||
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go-version }}
|
||||
@@ -24,9 +25,7 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
run: apt update -y && apt install -y --no-install-recommends git build-essential cmake pkg-config unzip libgtk2.0-dev curl ca-certificates libcurl4-openssl-dev libssl-dev libavcodec-dev libavformat-dev libswscale-dev libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libdc1394-22-dev
|
||||
uses: actions/checkout@v3
|
||||
- name: Get dependencies
|
||||
run: cd machinery && go mod download
|
||||
- name: Build
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -8,4 +8,6 @@ machinery/data/config
|
||||
machinery/data/cloud
|
||||
machinery/data/recordings
|
||||
machinery/data/snapshots
|
||||
machinery/test*
|
||||
machinery/test*
|
||||
machinery/init-dev.sh
|
||||
deployments/docker/private-docker-compose.yaml
|
||||
117
Dockerfile
117
Dockerfile
@@ -1,4 +1,5 @@
|
||||
FROM kerberos/base:977706d AS builder
|
||||
|
||||
FROM kerberos/base:dc12d68 AS build-machinery
|
||||
LABEL AUTHOR=Kerberos.io
|
||||
|
||||
ENV GOROOT=/usr/local/go
|
||||
@@ -11,9 +12,7 @@ ENV GOSUMDB=off
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git build-essential cmake pkg-config unzip libgtk2.0-dev \
|
||||
curl ca-certificates libcurl4-openssl-dev libssl-dev \
|
||||
libavcodec-dev libavformat-dev libswscale-dev libtbb2 libtbb-dev \
|
||||
libjpeg-dev libpng-dev libtiff-dev libdc1394-22-dev && \
|
||||
curl ca-certificates libcurl4-openssl-dev libssl-dev libjpeg62-turbo-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
##############################################################################
|
||||
@@ -21,31 +20,22 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
|
||||
RUN mkdir -p /go/src/github.com/kerberos-io/agent
|
||||
COPY machinery /go/src/github.com/kerberos-io/agent/machinery
|
||||
COPY ui /go/src/github.com/kerberos-io/agent/ui
|
||||
|
||||
########################
|
||||
# Download NPM and Yarns
|
||||
|
||||
RUN apt-get update && apt-get install -y curl && curl -sL https://deb.nodesource.com/setup_16.x | bash - && \
|
||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
|
||||
apt update && apt install yarn -y
|
||||
|
||||
##################################################################
|
||||
# Build Web
|
||||
# this will move the /build directory to ../machinery/www
|
||||
|
||||
RUN cd /go/src/github.com/kerberos-io/agent/ui && yarn && yarn build
|
||||
# Get the latest commit hash, so we know which version we're running
|
||||
COPY .git /go/src/github.com/kerberos-io/agent/.git
|
||||
RUN cd /go/src/github.com/kerberos-io/agent/.git && git log --format="%H" -n 1 | head -c7 > /go/src/github.com/kerberos-io/agent/machinery/version
|
||||
RUN cat /go/src/github.com/kerberos-io/agent/machinery/version
|
||||
|
||||
##################
|
||||
# Build Machinery
|
||||
|
||||
RUN cd /go/src/github.com/kerberos-io/agent/machinery && \
|
||||
go mod download && \
|
||||
go build main.go && \
|
||||
go build -tags timetzdata --ldflags '-s -w -extldflags "-static -latomic"' main.go && \
|
||||
mkdir -p /agent && \
|
||||
mv main /agent && \
|
||||
mv www /agent && \
|
||||
mv version /agent && \
|
||||
mv data /agent && \
|
||||
mkdir -p /agent/data/cloud && \
|
||||
mkdir -p /agent/data/snapshots && \
|
||||
@@ -65,44 +55,38 @@ RUN cp -r /agent ./
|
||||
####################################################################################
|
||||
# This will collect dependent libraries so they're later copied to the final image.
|
||||
|
||||
RUN /agent/main version
|
||||
RUN ldd /agent/main | tr -s '[:blank:]' '\n'
|
||||
RUN ldd /agent/main | tr -s '[:blank:]' '\n' | grep '^/' | \
|
||||
xargs -I % sh -c 'mkdir -p $(dirname ./%); cp % ./%;'
|
||||
RUN /dist/agent/main version
|
||||
|
||||
##########################################################
|
||||
# LDD doesnt always work in docker buildx (no idea why..)
|
||||
# Therefore we are moving some libraries manually
|
||||
###############################################
|
||||
# Build Bento4 -> we want fragmented mp4 files
|
||||
|
||||
RUN mkdir -p ./usr/lib
|
||||
ENV BENTO4_VERSION 1.6.0-639
|
||||
RUN cd /tmp && git clone https://github.com/axiomatic-systems/Bento4 && cd Bento4 && \
|
||||
git checkout tags/v${BENTO4_VERSION} && \
|
||||
cd Build && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release .. && \
|
||||
make && \
|
||||
mv /tmp/Bento4/Build/mp4fragment /dist/agent/ && \
|
||||
rm -rf /tmp/Bento4
|
||||
|
||||
RUN [ -f /lib64/ld-linux-x86-64.so.2 ] && $(mkdir -p lib64 && \
|
||||
cp /lib64/ld-linux-x86-64.so.2 lib64/) || echo "nothing to do here x86"
|
||||
FROM node:16.13.0-alpine3.11 AS build-ui
|
||||
|
||||
RUN [ -f /lib/ld-linux-aarch64.so.1 ] && $(mkdir -p lib/aarch64-linux-gnu && \
|
||||
cp /lib/ld-linux-aarch64.so.1 lib/ && \
|
||||
cp /lib/aarch64-linux-gnu/lib* lib/aarch64-linux-gnu/ && \
|
||||
cp /usr/lib/aarch64-linux-gnu/libopencv* usr/lib && \
|
||||
cp /usr/lib/aarch64-linux-gnu/libstdc* usr/lib && \
|
||||
cp /usr/lib/aarch64-linux-gnu/libx264* usr/lib ) || echo "nothing to do here arm64"
|
||||
########################
|
||||
# Build Web (React app)
|
||||
|
||||
RUN [ -f /usr/lib/arm-linux-gnueabihf/vfp/neon/libvpx.so.6 ] && \
|
||||
$(cp /usr/lib/arm-linux-gnueabihf/vfp/neon/libvpx.so.6 ./usr/lib/) || echo "nothing to do here armv7"
|
||||
RUN mkdir -p /go/src/github.com/kerberos-io/agent/machinery/www
|
||||
COPY ui /go/src/github.com/kerberos-io/agent/ui
|
||||
RUN cd /go/src/github.com/kerberos-io/agent/ui && yarn && yarn build
|
||||
|
||||
RUN cp -r /usr/local/lib/libavcodec* ./usr/lib && \
|
||||
cp -r /usr/local/lib/libavformat* ./usr/lib && \
|
||||
cp -r /usr/local/lib/libavfilter* ./usr/lib && \
|
||||
cp -r /usr/local/lib/libavutil* ./usr/lib && \
|
||||
cp -r /usr/local/lib/libavresample* ./usr/lib && \
|
||||
cp -r /usr/local/lib/libavdevice* ./usr/lib && \
|
||||
cp -r /usr/local/lib/libswscale* ./usr/lib && \
|
||||
cp -r /usr/local/lib/libswresample* ./usr/lib && \
|
||||
cp -r /usr/local/lib/libpostproc* ./usr/lib
|
||||
####################################
|
||||
# Let's create a /dist folder containing just the files necessary for runtime.
|
||||
# Later, it will be copied as the / (root) of the output image.
|
||||
|
||||
# As mentioned before, above is really a hack as LDD
|
||||
# doesn't work always in docker buildx. You might not need this
|
||||
# when doing a local build.
|
||||
################################################################
|
||||
WORKDIR /dist
|
||||
RUN mkdir -p ./agent && cp -r /go/src/github.com/kerberos-io/agent/machinery/www ./agent/
|
||||
|
||||
############################################
|
||||
# Publish main binary to GitHub release
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
@@ -114,48 +98,49 @@ RUN addgroup -S kerberosio && adduser -S agent -G kerberosio && addgroup agent v
|
||||
#################################
|
||||
# Copy files from previous images
|
||||
|
||||
COPY --chown=0:0 --from=builder /dist /
|
||||
COPY --chown=0:0 --from=builder /usr/local/go/lib/time/zoneinfo.zip /zoneinfo.zip
|
||||
COPY --chown=0:0 --from=build-machinery /dist /
|
||||
COPY --chown=0:0 --from=build-ui /dist /
|
||||
|
||||
ENV ZONEINFO=/zoneinfo.zip
|
||||
|
||||
RUN apk update && apk add ca-certificates --no-cache && \
|
||||
apk add tzdata curl --no-cache && rm -rf /var/cache/apk/*
|
||||
|
||||
#################
|
||||
# Install Bento4
|
||||
RUN cd && wget https://www.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-639.x86_64-unknown-linux.zip && \
|
||||
unzip Bento4-SDK-1-6-0-639.x86_64-unknown-linux.zip && rm Bento4-SDK-1-6-0-639.x86_64-unknown-linux.zip && \
|
||||
cp ~/Bento4-SDK-1-6-0-639.x86_64-unknown-linux/bin/mp4fragment /usr/bin/
|
||||
RUN apk update && apk add ca-certificates curl libstdc++ libc6-compat --no-cache && rm -rf /var/cache/apk/*
|
||||
|
||||
##################
|
||||
# Try running agent
|
||||
|
||||
RUN mv /agent/* /home/agent/
|
||||
RUN cp /home/agent/mp4fragment /usr/local/bin/
|
||||
RUN /home/agent/main version
|
||||
|
||||
#######################
|
||||
# Make template config
|
||||
|
||||
RUN cp /home/agent/data/config/config.json /home/agent/data/config.template.json
|
||||
|
||||
###########################
|
||||
# Set permissions correctly
|
||||
|
||||
RUN chown -R agent:kerberosio /home/agent/data
|
||||
|
||||
###########################
|
||||
# Grant the necessary root capabilities to the process trying to bind to the privileged port
|
||||
RUN apk add libcap && setcap 'cap_net_bind_service=+ep' /home/agent/main
|
||||
|
||||
###################
|
||||
# Run non-root user
|
||||
|
||||
USER agent
|
||||
|
||||
######################################
|
||||
# By default the app runs on port 8080
|
||||
# By default the app runs on port 80
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 80
|
||||
|
||||
######################################
|
||||
# Check if agent is still running
|
||||
|
||||
HEALTHCHECK CMD curl --fail http://localhost:8080 || exit 1
|
||||
HEALTHCHECK CMD curl --fail http://localhost:80 || exit 1
|
||||
|
||||
###################################################
|
||||
# Leeeeettttt'ssss goooooo!!!
|
||||
# Run the shizzle from the right working directory.
|
||||
WORKDIR /home/agent
|
||||
CMD ["./main", "run", "opensource", "8080"]
|
||||
CMD ["./main", "run", "opensource", "80"]
|
||||
|
||||
253
README.md
253
README.md
@@ -2,8 +2,6 @@
|
||||
|
||||
<a target="_blank" href="https://kerberos.io"><img src="https://img.shields.io/badge/kerberos-website-gray.svg?longCache=true&colorB=brightgreen" alt="Kerberos Agent"></a>
|
||||
<a target="_blank" href="https://doc.kerberos.io"><img src="https://img.shields.io/badge/kerberos-documentation-gray.svg?longCache=true&colorB=brightgreen" alt="Kerberos Agent"></a>
|
||||
<a target="_blank" href="https://twitter.com/kerberosio?ref_src=twsrc%5Etfw"><img src="https://img.shields.io/twitter/url.svg?label=Follow%20%40kerberosio&style=social&url=https%3A%2F%2Ftwitter.com%2Fkerberosio" alt="Twitter Widget"></a>
|
||||
<a target="_blank" href="https://join.slack.com/t/kerberosio/shared_invite/zt-1a5oj4pwm-O4qCAN9c5r2um0Ns0ge8ww"><img src="https://img.shields.io/badge/slack-@kerberosio-yellow.svg?logo=slack " alt="Kerberos.io"></a>
|
||||
|
||||
<a target="_blank" href="https://circleci.com/gh/kerberos-io/agent"><img src="https://circleci.com/gh/kerberos-io/agent.svg?style=svg"/></a>
|
||||
<img src="https://github.com/kerberos-io/agent/workflows/Go/badge.svg"/>
|
||||
@@ -17,97 +15,200 @@
|
||||
<a target="_blank" href="https://www.figma.com/proto/msuYC6sv2cOCqZeDtBxNy7/%5BNEW%5D-Kerberos.io-Apps?node-id=1%3A1788&viewport=-490%2C191%2C0.34553584456443787&scaling=min-zoom&page-id=1%3A2%3Ffuid%3D449684443467913607" alt="Kerberos Agent"></a>
|
||||
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
||||
[](
|
||||
https://brianmacdonald.github.io/Ethonate/address#0xf4a759C9436E2280Ea9cdd23d3144D95538fF4bE)
|
||||
[](https://brianmacdonald.github.io/Ethonate/address#0xf4a759C9436E2280Ea9cdd23d3144D95538fF4bE)
|
||||
<a target="_blank" href="https://twitter.com/kerberosio?ref_src=twsrc%5Etfw"><img src="https://img.shields.io/twitter/url.svg?label=Follow%20%40kerberosio&style=social&url=https%3A%2F%2Ftwitter.com%2Fkerberosio" alt="Twitter Widget"></a>
|
||||
[](https://discord.gg/Bj77Vqfp2G)
|
||||
|
||||
[**Docker Hub**](https://hub.docker.com/r/kerberos/agent) | [**Documentation**](https://doc.kerberos.io) | [**Website**](https://kerberos.io)
|
||||
[**Docker Hub**](https://hub.docker.com/r/kerberos/agent) | [**Documentation**](https://doc.kerberos.io) | [**Website**](https://kerberos.io) | [**View Demo**](https://demo.kerberos.io)
|
||||
|
||||
Kerberos Agent is a cutting edge video surveillance management system made available as Open Source under the MIT License. This means that all the source code is available for you or your company, and you can use, transform and distribute the source code; as long you keep a reference of the original license. Kerberos Agent can be used for commercial usage (which was not the case for v2). Read more [about the license here](LICENSE).
|
||||
Kerberos Agent is an isolated and scalable video (surveillance) management agent made available as Open Source under the MIT License. This means that all the source code is available for you or your company, and you can use, transform and distribute the source code; as long you keep a reference of the original license. Kerberos Agent can be used for commercial usage (which was not the case for v2). Read more [about the license here](LICENSE).
|
||||
|
||||

|
||||
|
||||
## :thinking: Prerequisites
|
||||
|
||||
- An IP camera which supports a RTSP H264 encoded stream,
|
||||
- (or) a USB camera, Raspberry Pi camera or other camera, that [you can tranform to a valid RTSP stream](https://github.com/kerberos-io/camera-to-rtsp).
|
||||
- Any hardware that can run a container, for example: a Raspberry Pi, NVidia Jetson, Intel NUC, a VM, Bare metal machine or a full blown Kubernetes cluster.
|
||||
|
||||
## :video_camera: Is my camera working?
|
||||
|
||||
There are a myriad of cameras out there (USB, IP and other cameras), and it might be daunting to know if Kerberos Agent will work for your camera. [Therefore we are listing all the camera models that are acknowlegded by the community](https://github.com/kerberos-io/agent/issues/59). Feel free to add your camera to the list as well!
|
||||
|
||||
## :books: Overview
|
||||
|
||||
### Up and running in no time
|
||||
|
||||
1. [Quickstart - Docker](#quickstart---docker)
|
||||
2. [Quickstart - Balena](#quickstart---balena)
|
||||
|
||||
### Introduction
|
||||
|
||||
3. [A world of Kerberos Agents](#a-world-of-kerberos-agents)
|
||||
|
||||
### Running and automation
|
||||
|
||||
4. [How to run and deploy a Kerberos Agent](#how-to-run-and-deploy-a-kerberos-agent)
|
||||
5. [Access the Kerberos Agent](#access-the-kerberos-agent)
|
||||
6. [Configure and persist with volume mounts](#configure-and-persist-with-volume-mounts)
|
||||
7. [Configure with environment variables](#configure-with-environment-variables)
|
||||
|
||||
### Contributing
|
||||
|
||||
8. [Contribute with Codespaces](#contribute-with-codespaces)
|
||||
9. [Develop and build](#develop-and-build)
|
||||
10. [Building from source](#building-from-source)
|
||||
11. [Building for Docker](#building-for-docker)
|
||||
|
||||
### Varia
|
||||
|
||||
12. [Support our project](#support-our-project)
|
||||
13. [What is new?](#what-is-new)
|
||||
14. [Contributors](#contributors)
|
||||
|
||||
## Quickstart - Docker
|
||||
|
||||
The easiest to get your Kerberos Agent up and running is to use our Docker image on [Docker hub](https://hub.docker.com/r/kerberos/agent). Once you selected a specific tag, run below command, which will open the web interface of your Kerberos agent on port `8080`. For persisting your configuration and/or recordings [attach a volume](#attach-a-volume).
|
||||
|
||||
docker run -p 8080:8080 --name mycamera -d kerberos/agent:latest
|
||||
The easiest to get your Kerberos Agent up and running is to use our public image on [Docker hub](https://hub.docker.com/r/kerberos/agent). Once you have selected a specific tag, run below `docker` command, which will open the web interface of your Kerberos agent on port `80`, and off you go. For a more configurable and persistent deployment have a look at [Running and automating a Kerberos Agent](#running-and-automating-a-kerberos-agent).
|
||||
|
||||
docker run -p 80:80 --name mycamera -d --restart=always kerberos/agent:latest
|
||||
|
||||
If you want to connect to an USB or Raspberry Pi camera, [you'll need to run our side car container](https://github.com/kerberos-io/camera-to-rtsp) which proxy the camera to an RTSP stream. In that case you'll want to configure the Kerberos Agent container to run in the host network, so it can connect directly to the RTSP sidecar.
|
||||
|
||||
docker run --network=host --name mycamera -d --restart=always kerberos/agent:latest
|
||||
|
||||
## Quickstart - Balena
|
||||
|
||||
To be written
|
||||
|
||||
Run Kerberos Agent with Balena super powers. Monitor your agent with seamless remote access, and an encrypted https endpoint.
|
||||
Checkout our fleet on [Balena Hub](https://hub.balena.io/fleets?0%5B0%5D%5Bn%5D=any&0%5B0%5D%5Bo%5D=full_text_search&0%5B0%5D%5Bv%5D=agent), and add your agent.
|
||||
|
||||
## Quickstart - Snap
|
||||
[](https://dashboard.balena-cloud.com/deploy?repoUrl=https://github.com/kerberos-io/agent)
|
||||
|
||||
To be written
|
||||
**_Work In Progress_** - Currently we only support IP and USB Cameras, we have [an approach for leveraging the Raspberry Pi camera](https://github.com/kerberos-io/camera-to-rtsp), but this isn't working as expected with Balena. If you require this, you'll need to use the traditional Docker deployment with sidecar as mentioned above.
|
||||
|
||||
## Introduction
|
||||
## A world of Kerberos Agents
|
||||
|
||||
Kerberos.io is a cutting edge video surveillance system with a strong focus on user experience, scalability, resilience, extension and integration. Kerberos.io provides different solutions, but from a high level point of view it comes into two flavours: Kerberos Agent and Kerberos Enterprise Suite. Bottom line Kerberos Enterprise Suite extends Kerberos Agent with additional components such as Kerberos Factory, Kerberos Vault and Kerberos Hub.
|
||||
The Kerberos Agent is an isolated and scalable video (surveillance) management agent with a strong focus on user experience, scalability, resilience, extension and integration. Next to the Kerberos Agent, Kerberos.io provides many other tools such as [Kerberos Factory](https://github.com/kerberos-io/factory), [Kerberos Vault](https://github.com/kerberos-io/vault) and [Kerberos Hub](https://github.com/kerberos-io/hub) to provide additional capabilities: bring your own cloud, bring your own storage, central overview, live streaming, machine learning etc.
|
||||
|
||||
### Kerberos Agent
|
||||
As mentioned above Kerberos.io applies the concept of agents. An agent is running next to (or on) your camera, and is processing a single camera feed. It applies motion based or continuous recording and make those recordings available through a user friendly web interface. A Kerberos Agent allows you to connect to other cloud services or integrates with custom applications. Kerberos Agent is used for personal usage and scales to enterprise production level deployments.
|
||||
|
||||
- Installation in seconds (Docker, Balena, Snap, etc).
|
||||
This repository contains everything you'll need to know about our core product, Kerberos Agent. Below you'll find a brief list of features and functions.
|
||||
|
||||
- Low memory and CPU usage.
|
||||
- Simplified and modern user interface.
|
||||
- Multi architecture (ARMv7, ARMv8, amd64, etc).
|
||||
- Multi camera support: IP Cameras (MJPEG/H264), USB cameras and Raspberry Pi Cameras through a RTSP proxy.
|
||||
- Single camera per instance (e.g. One Docker container per camera).
|
||||
- Integrations (Webhooks, MQTT, Script, etc).
|
||||
- Cloud storage (Kerberos Hub, Kerberos Vault, Minio, Storj, etc).
|
||||
- Multi camera support: IP Cameras (H264), USB cameras and Raspberry Pi Cameras [through a RTSP proxy](https://github.com/kerberos-io/camera-to-rtsp).
|
||||
- Single camera per instance (e.g. oneå container per camera).
|
||||
- Ability to specifiy conditions: motion region, time table, continuous recording, etc.
|
||||
- [Deploy where you want](#how-to-run-and-deploy-a-kerberos-agent) with the tools you use: `docker`, `docker compose`, `ansible`, `terraform`, `kubernetes`, etc.
|
||||
- Cloud storage (Kerberos Hub, Kerberos Vault). WIP: Minio, Storj, etc.
|
||||
- WIP: Integrations (Webhooks, MQTT, Script, etc).
|
||||
- MIT License
|
||||
|
||||
### Kerberos Factory (part of [Kerberos Enterprise suite](https://doc.kerberos.io/enterprise/first-things-first/))
|
||||
## How to run and deploy a Kerberos Agent
|
||||
|
||||
- Installation on top of Kubernetes (K8S).
|
||||
- Camera support for IP camera only (RTSP/H264).
|
||||
- Massive horizontal scaling, thanks to Kubernetes.
|
||||
- Management of multiple Kerberos Agents through a single pane of glass.
|
||||
- Low memory and CPU intensive.
|
||||
- Modular and extensible design for building own extensions and integrations (e.g. a video analytics platform).
|
||||
- Commercial licensed and closed source.
|
||||
As described before a Kerberos Agent is a container, which can be deployed through various ways and automation tools such as `docker`, `docker compose`, `kubernetes` and the list goes on. To simplify your life we have come with concrete and working examples of deployments to help you speed up your Kerberos.io journey.
|
||||
|
||||
## How it works: A world of Agents 🕵🏼♂️
|
||||
We have documented the different deployment models [in the `deployments` directory](https://github.com/kerberos-io/agent/tree/master/deployments) of this repository. There you'll learn and find how to deploy using:
|
||||
|
||||
Kerberos.io applies the concept of agents. An agent is running next to or on your camera, and is processing a single camera feed. It applies motion based recording and make those recordings available through a user friendly web interface. Kerberos Agent allows you to connect to other cloud services or custom applications. Kerberos Agent is perfect for personal usage and/or is a great tool if you only have a couple of surveillance cameras to be processed.
|
||||
- [Docker](https://github.com/kerberos-io/agent/tree/master/deployments#1-docker)
|
||||
- [Docker Compose](https://github.com/kerberos-io/agent/tree/master/deployments#2-docker-compose)
|
||||
- [Kubernetes](https://github.com/kerberos-io/agent/tree/master/deployments#3-kubernetes)
|
||||
- [Red Hat OpenShift with Ansible](https://github.com/kerberos-io/agent/tree/master/deployments#4-red-hat-ansible-and-openshift)
|
||||
- [Terraform](https://github.com/kerberos-io/agent/tree/master/deployments#5-terraform)
|
||||
- [Salt](https://github.com/kerberos-io/agent/tree/master/deployments#6-salt)
|
||||
|
||||
If you are looking for a solution that scales better with your video surveillance and/or video analytics requirements, [Kerberos Enterprise Suite might be a better fit](https://doc.kerberos.io/enterprise/first-things-first).
|
||||
By default your Kerberos Agents will store all its configuration and recordings inside the container. To help you automate and have a more consistent data governance, you can attach volumes to configure and persist data of your Kerberos Agents, and/or configure each Kerberos Agent through environment variables.
|
||||
|
||||
## Running as a container
|
||||
## Access the Kerberos Agent
|
||||
|
||||
We are creating Docker images as part of our CI/CD process. You'll find our Docker images on [Docker hub](https://hub.docker.com/r/kerberos/agent). Pick a specific tag of choice, or use latest. Once done run below command, this will open the web interface of your Kerberos agent on port 8080.
|
||||
|
||||
docker run -p 8080:8080 --name mycamera -d kerberos/agent:latest
|
||||
Once you have deployed the Kerberos Agent, using one of the deployment models described above, you will be able to access the Kerberos Agent user interface. A login page is presented asking for some credentials.
|
||||
|
||||
Or for a develop build:
|
||||
The default username and password for the Kerberos Agent is:
|
||||
|
||||
docker run -p 8080:8080 --name mycamera -d kerberos/agent-dev:latest
|
||||
- Username: `root`
|
||||
- Password: `root`
|
||||
|
||||
Feel free to use another port if your host system already has a workload running on `8080`. For example `8082`.
|
||||
**_Please note that you change the username and password for a final installation, see [Configure with environment variables](#configure-with-environment-variables) below._**
|
||||
|
||||
docker run -p 8082:8080 --name mycamera -d kerberos/agent:latest
|
||||
## Configure and persist with volume mounts
|
||||
|
||||
## Attach a volume
|
||||
An example of how to mount a host directory is shown below using `docker`, but is applicable for [all the deployment models and tools described above](#running-and-automating-a-kerberos-agent).
|
||||
|
||||
By default your Kerberos agent will store all its configuration and recordings inside the container. It might be interesting to store both configuration and your recordings outside the container, on your local disk. This helps persisting your storage even after you decide to wipe out your Kerberos agent.
|
||||
You attach a volume to your container by leveraging the `-v` option. To mount your own configuration file and recordings folder, execute as following:
|
||||
|
||||
You attach a volume to your container by leveraging the `-v` option. To mount your own configuration file, execute as following:
|
||||
docker run -p 80:80 --name mycamera \
|
||||
-v $(pwd)/agent/config:/home/agent/data/config \
|
||||
-v $(pwd)/agent/recordings:/home/agent/data/recordings \
|
||||
-d --restart=always kerberos/agent:latest
|
||||
|
||||
1. Decide where you would like to store your configuration and recordings; create a new directory for the config file and recordings folder accordingly.
|
||||
More example [can be found in the deployment section](https://github.com/kerberos-io/agent/tree/master/deployments) for each deployment and automation tool.
|
||||
|
||||
mkdir agent
|
||||
mkdir agent/config
|
||||
mkdir agent/recordings
|
||||
## Configure with environment variables
|
||||
|
||||
2. Once you have located your desired directory, copy the latest [`config.json`](https://github.com/kerberos-io/agent/blob/master/machinery/data/config/config.json) file into your config directory.
|
||||
Next to attaching the configuration file, it is also possible to override the configuration with environment variables. This makes deployments easier when leveraging `docker compose` or `kubernetes` deployments much easier and scalable. Using this approach we simplify automation through `ansible` and `terraform`.
|
||||
|
||||
wget https://raw.githubusercontent.com/kerberos-io/agent/master/machinery/data/config/config.json -O agent/config/config.json
|
||||
docker run -p 80:80 --name mycamera \
|
||||
-e AGENT_NAME=mycamera \
|
||||
-e AGENT_TIMEZONE=Europe/Brussels \
|
||||
-e AGENT_CAPTURE_IPCAMERA_RTSP=rtsp://fake.kerberos.io/stream \
|
||||
-e AGENT_CAPTURE_CONTINUOUS=true \
|
||||
-d --restart=always kerberos/agent:latest
|
||||
|
||||
3. Run the docker command as following to attach your config directory and recording directory.
|
||||
| Name | Description | Default Value |
|
||||
| --------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------- |
|
||||
| `AGENT_USERNAME` | The username used to authenticate against the Kerberos Agent login page. | "root" |
|
||||
| `AGENT_PASSWORD` | The password used to authenticate against the Kerberos Agent login page. | "root" |
|
||||
| `AGENT_KEY` | A unique identifier for your Kerberos Agent, this is auto-generated but can be overriden. | "" |
|
||||
| `AGENT_NAME` | The agent friendly-name. | "agent" |
|
||||
| `AGENT_TIMEZONE` | Timezone which is used for converting time. | "Africa/Ceuta" |
|
||||
| `AGENT_OFFLINE` | Makes sure no external connection is made. | "false" |
|
||||
| `AGENT_AUTO_CLEAN` | Cleans up the recordings directory. | "true" |
|
||||
| `AGENT_AUTO_CLEAN_MAX_SIZE` | If `AUTO_CLEAN` enabled, set the max size of the recordings directory in (MB). | "100" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_RTSP` | Full-HD RTSP endpoint to the camera you're targetting. | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_SUB_RTSP` | Sub-stream RTSP endpoint used for livestreaming (WebRTC). | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_ONVIF` | Mark as a compliant ONVIF device. | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_ONVIF_XADDR` | ONVIF endpoint/address running on the camera. | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_ONVIF_USERNAME` | ONVIF username to authenticate against. | "" |
|
||||
| `AGENT_CAPTURE_IPCAMERA_ONVIF_PASSWORD` | ONVIF password to authenticate against. | "" |
|
||||
| `AGENT_CAPTURE_RECORDING` | Toggle for enabling making recordings. | "true" |
|
||||
| `AGENT_CAPTURE_CONTINUOUS` | Toggle for enabling continuous or motion based recording. | "false" |
|
||||
| `AGENT_CAPTURE_PRERECORDING` | If `CONTINUOUS` set to `false`, specify the recording time (seconds) before after motion event. | "10" |
|
||||
| `AGENT_CAPTURE_POSTRECORDING` | If `CONTINUOUS` set to `false`, specify the recording time (seconds) after motion event. | "20" |
|
||||
| `AGENT_CAPTURE_MAXLENGTH` | The maximum length of a single recording (seconds). | "30" |
|
||||
| `AGENT_CAPTURE_PIXEL_CHANGE` | If `CONTINUOUS` set to `false`, the number of pixel require to change before motion triggers. | "150" |
|
||||
| `AGENT_CAPTURE_FRAGMENTED` | Set the format of the recorded MP4 to fragmented (suitable for HLS). | "false" |
|
||||
| `AGENT_CAPTURE_FRAGMENTED_DURATION` | If `AGENT_CAPTURE_FRAGMENTED` set to `true`, define the duration (seconds) of a fragment. | "8" |
|
||||
| `AGENT_CLOUD` | Store recordings in a Kerberos Hub (s3) or your Kerberos Vault (kstorage). | "s3" |
|
||||
| `AGENT_HUB_URI` | The Kerberos Hub API, defaults to our Kerberos Hub SAAS. | "https://api.cloud.kerberos.io" |
|
||||
| `AGENT_HUB_KEY` | The access key linked to your account in Kerberos Hub. | "" |
|
||||
| `AGENT_HUB_PRIVATE_KEY` | The secret access key linked to your account in Kerberos Hub. | "" |
|
||||
| `AGENT_HUB_USERNAME` | Your Kerberos Hub username, which owns the above access and secret keys. | "" |
|
||||
| `AGENT_HUB_SITE` | The site ID of a site you've created in your Kerberos Hub account. | "" |
|
||||
| `AGENT_MQTT_URI` | A MQTT broker endpoint that is used for bi-directional communication (live view, onvif, etc) | "tcp://mqtt.kerberos.io:1883" |
|
||||
| `AGENT_MQTT_USERNAME` | Username of the MQTT broker. | "" |
|
||||
| `AGENT_MQTT_PASSWORD` | Password of the MQTT broker. | "" |
|
||||
| `AGENT_STUN_URI` | When using WebRTC, you'll need to provide a STUN server. | "stun:turn.kerberos.io:8443" |
|
||||
| `AGENT_TURN_URI` | When using WebRTC, you'll need to provide a TURN server. | "turn:turn.kerberos.io:8443" |
|
||||
| `AGENT_TURN_USERNAME` | TURN username used for WebRTC. | "username1" |
|
||||
| `AGENT_TURN_PASSWORD` | TURN password used for WebRTC. | "password1" |
|
||||
| `AGENT_KERBEROSVAULT_URI` | The Kerberos Vault API url. | "" . |
|
||||
| `AGENT_KERBEROSVAULT_ACCESS_KEY` | The access key of a Kerberos Vault account. | "" |
|
||||
| `AGENT_KERBEROSVAULT_SECRET_KEY` | The secret key of a Kerberos Vault account. | "" |
|
||||
| `AGENT_KERBEROSVAULT_PROVIDER` | A Kerberos Vault provider you have created (optional). | "" |
|
||||
| `AGENT_KERBEROSVAULT_DIRECTORY` | The directory, in the provider, where the recordings will be stored in. | "" |
|
||||
|
||||
## Contribute with Codespaces
|
||||
|
||||
One of the major blockers for letting you contribute to an Open Source project is to setup your local development machine. Why? Because you might have already some tools and libraries installed that are used for other projects, and the libraries you would need for Kerberos Agent, for example FFmpeg, might require a different version. Welcome to the dependency hell..
|
||||
|
||||
By leveraging codespaces, which the Kerberos Agent repo supports, you will be able to setup the required development environment in a few minutes. By opening the `<> Code` tab on the top of the page, you will be able to create a codespace, [using the Kerberos Devcontainer](https://github.com/kerberos-io/devcontainer) base image. This image requires all the relevant dependencies: FFmpeg, OpenCV, Golang, Node, Yarn, etc.
|
||||
|
||||

|
||||
|
||||
After a few minutes, you will see a beautiful `Visual Studio Code` shown in your browser, and you are ready to code!
|
||||
|
||||

|
||||
|
||||
docker run -p 8080:8080 --name mycamera -v $(pwd)/agent/config:/home/agent/data/config -v $(pwd)/agent/recordings:/home/agent/data/recordings -d kerberos/agent:latest
|
||||
|
||||
## Develop and build
|
||||
|
||||
Kerberos Agent is divided in two parts a `machinery` and `web`. Both parts live in this repository in their relative folders. For development or running the application on your local machine, you have to run both the `machinery` and the `web` as described below. When running in production everything is shipped as only one artifact, read more about this at [Building for production](#building-for-production).
|
||||
@@ -136,13 +237,13 @@ You can simply run the `machinery` using following commands.
|
||||
|
||||
git clone https://github.com/kerberos-io/agent
|
||||
cd machinery
|
||||
go run main.go run mycameraname 8080
|
||||
go run main.go run mycameraname 80
|
||||
|
||||
This will launch the Kerberos Agent and run a webserver on port `8080`. You can change the port by your own preference. We strongly support the usage of [Goland](https://www.jetbrains.com/go/) or [Visual Studio Code](https://code.visualstudio.com/), as it comes with all the debugging and linting features builtin.
|
||||
This will launch the Kerberos Agent and run a webserver on port `80`. You can change the port by your own preference. We strongly support the usage of [Goland](https://www.jetbrains.com/go/) or [Visual Studio Code](https://code.visualstudio.com/), as it comes with all the debugging and linting features builtin.
|
||||
|
||||

|
||||

|
||||
|
||||
## Building for Production
|
||||
## Building from source
|
||||
|
||||
Running Kerberos Agent in production only require a single binary to run. Nevertheless, we have two parts, the `machinery` and the `web`, we merge them during build time. So this is what happens.
|
||||
|
||||
@@ -157,14 +258,14 @@ To build the Kerberos Agent web app, you simply have to run the `build` command
|
||||
|
||||
Building the `machinery` is also super easy 🚀, by using `go build` you can create a single binary which ships it all; thank you Golang. After building you will endup with a binary called `main`, this is what contains everything you need to run Kerberos Agent.
|
||||
|
||||
Remember the build step of the `web` part, during build time we move the build directory to the `machinery` directory. Inside the `machinery` web server [we reference the](https://github.com/kerberos-io/agent/blob/master/machinery/src/routers/http/Server.go#L44) `build` directory. This makes it possible to just a have single web server that runs it all.
|
||||
Remember the build step of the `web` part, during build time we move the build directory to the `machinery` directory. Inside the `machinery` web server [we reference the](https://github.com/kerberos-io/agent/blob/master/machinery/src/routers/http/Server.go#L44) `build` directory. This makes it possible to just a have single web server that runs it all.
|
||||
|
||||
cd machinery
|
||||
go build
|
||||
|
||||
## Building for Docker
|
||||
|
||||
Inside the root of this `agent` repository, you will find a `Dockerfile`. This file contains the instructions for building and shipping **Kerberos Agent**. Important to note is that start from a prebuild base image, `kerberos/debian-opencv-ffmpeg:1.0.xxx`.
|
||||
Inside the root of this `agent` repository, you will find a `Dockerfile`. This file contains the instructions for building and shipping **Kerberos Agent**. Important to note is that start from a prebuild base image, `kerberos/base:xxx`.
|
||||
This base image contains already a couple of tools, such as Golang, FFmpeg and OpenCV. We do this for faster compilation times.
|
||||
|
||||
By running the `docker build` command, you will create the Kerberos Agent Docker image. After building you can simply run the image as a Docker container.
|
||||
@@ -174,12 +275,12 @@ By running the `docker build` command, you will create the Kerberos Agent Docker
|
||||
## Support our project
|
||||
|
||||
If you like our product please feel free to execute an Ethereum donation. All donations will flow back and split to our Open Source contributors, as they are the heart of this community.
|
||||
|
||||
|
||||
<img width="272" alt="Ethereum donation linke" src="https://user-images.githubusercontent.com/1546779/173443671-3d773068-ae10-4862-a990-dc7c89f3d9c2.png">
|
||||
|
||||
Ethereum Address: `0xf4a759C9436E2280Ea9cdd23d3144D95538fF4bE`
|
||||
|
||||
## Previous releases
|
||||
|
||||
## What is new?
|
||||
|
||||
This repository contains the next generation of Kerberos.io, **Kerberos Agent (v3)**, and is the successor of the machinery and web repositories. A switch in technologies and architecture has been made. This version is still under active development and can be followed on the [develop branch](https://github.com/kerberos-io/agent/tree/develop) and [project overview](https://github.com/kerberos-io/agent/projects/1).
|
||||
|
||||
@@ -187,34 +288,6 @@ Read more about this [at the FAQ](#faq) below.
|
||||
|
||||

|
||||
|
||||
## FAQ
|
||||
|
||||
#### 1. Why a mono repo?
|
||||
|
||||
We have noticed in the past (v1 and v2) splitting the repositories (machinery and web), created a lot of confusion within our community. People didn't understand the different versions and so on. This caused a lack of collaboration, and made it impossible for some people to collaborate and contribute.
|
||||
|
||||
Having a mono repo, which is well organised, simplifies the entry point for new people who would like to use, understand and/or contribute to Kerberos Agent.
|
||||
|
||||
#### 2. Why a change in technologies?
|
||||
|
||||
In previous versions (v1 and v2) we used technologies like C++, PHP and BackboneJS. 7 years ago this was still acceptable, however time has changed and new technologies such as React and Golang became very popular.
|
||||
|
||||
Due to previous reason we have decided to rebuild the Kerberos Agent technology from scratch, taking into account all the feedback we acquired over the years. Having these technologies available, we will enable more people to contribute and use our technology.
|
||||
|
||||
#### 3. What is the difference with Kerberos Enterprise?
|
||||
|
||||
We started the developments of Kerberos Enterprise a year ago (January, 2020), our focus here was scalability, and fast development and easy deployment. We noticed that with technologies such as Golang and React, we can still provide a highly performant video surveillance system.
|
||||
|
||||
Kerberos Agent uses the same technology stack, and some code pieces, of Kerberos Enterprise which we have already build. We have a very clear now, of how a well developed and documented video surveillance system needs to look like.
|
||||
|
||||
#### 4. When are we going to be able to install the first version?
|
||||
|
||||
We plan to ship the first version by the end of Q1, afterwards we will add more and more features as usual.
|
||||
|
||||
#### 5. Change in License
|
||||
|
||||
Kerberos Agent (v3) is now available under the MIT license.
|
||||
|
||||
## Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
|
||||
BIN
assets/img/codespace-vscode.png
Normal file
BIN
assets/img/codespace-vscode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 769 KiB |
BIN
assets/img/codespace.png
Normal file
BIN
assets/img/codespace.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 286 KiB |
BIN
assets/img/logo.png
Normal file
BIN
assets/img/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/img/vscode-desktop.png
Normal file
BIN
assets/img/vscode-desktop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 966 KiB |
53
deployments/README.md
Normal file
53
deployments/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Kerberos Agent Deployments
|
||||
|
||||
Great to see you here, you just arrived at the real stuff! As you may have understood Kerberos Agent is a containerized solution. A Kerberos Agent, or container equivalent, is running for each camera. This approach makes it scalable, isolated and probably the most important thing an exceptional workload governance.
|
||||
|
||||
Due to it's nature, of acting as a micro service, there are many different ways how to get this Kerberos Agent up and running. This part of the Kerberos Agent repository contains example configurations, for all the different deployments and automations you can leverage to deploy and scale your video landscape.
|
||||
|
||||
We will discuss following deployment models.
|
||||
|
||||
- [1. Docker](#1-docker)
|
||||
- [2. Docker Compose](#2-docker-compose)
|
||||
- [3. Kubernetes](#3-kubernetes)
|
||||
- [4. Red Hat Ansible and OpenShift](#4-red-hat-ansible-and-openshift)
|
||||
- [5. Kerberos Factory](#5-kerberos-factory)
|
||||
- [6. Terraform](#6-terraform)
|
||||
- [7. Salt](#7-salt)
|
||||
|
||||
## 1. Docker
|
||||
|
||||
Leveraging `docker` is probably one of the easiest way to run and test the Kerberos Agent. Thanks to it's multi-architecture images you could run it on almost every machine. The `docker` approach is perfect for running one or two cameras in a (single machine) home deployment, a POC to verify its capabilities, or testing if your old/new IP camera is operational with our Kerberos Agent.
|
||||
|
||||
> Learn more [about Kerberos Agent on Docker here](https://github.com/kerberos-io/agent/tree/master/deployments/docker#1-running-a-single-container).
|
||||
|
||||
## 2. Docker Compose
|
||||
|
||||
If you consider `docker` as "your way to go", but require to run a bigger (single machine) deployment at home or inside your store then `docker compose` would be more suitable. By specifying a single `docker-compose.yaml` file, you can define all your Kerberos Agents (and thus cameras) in a single file, with a custom configuration to fit your needs.
|
||||
|
||||
> Learn more [about Kerberos Agent with Docker Compose here](https://github.com/kerberos-io/agent/tree/master/deployments/docker#2-running-multiple-containers-with-docker-compose).
|
||||
|
||||
## 3. Kubernetes
|
||||
|
||||
As described above, `docker` is a great tool for smaller deployments, where you are just running on a single machine and want to ramp up quickly. As you might expect, this is a not an ideal situation for production deployments. Kubernetes can help you to build a scalable, flexible and resilient deployment.
|
||||
|
||||
> Learn more [about Kerberos Agent in a Kubernetes cluster here](https://github.com/kerberos-io/agent/tree/master/deployments/kubernetes).
|
||||
|
||||
## 4. Red Hat Ansible and OpenShift
|
||||
|
||||
If you running an alternative distribution such as Red Hat OpenShift, things will work out exactly as mentioned before with the `Kubernetes` deployment. You'll have all the benefints of Red Hat OpenShift on top. One of the things we provide here is an Ansible playbook to deploy the Kerberos Agent in the OpenShift cluster.
|
||||
|
||||
> Learn more [about Kerberos Agent in OpenShift with Ansible](https://github.com/kerberos-io/agent/tree/master/deployments/ansible-openshift).
|
||||
|
||||
## 5. Kerberos Factory
|
||||
|
||||
All of the previously deployments, `docker`, `kubernetes` and `openshift` are great for a technical audience. However for business users, it might be more convenient to have a clean web ui, that one can leverage to add one or more cameras (Kerberos Agents), without the hassle of the technical resources.
|
||||
|
||||
> Learn more [about Kerberos Agent with Kerberos Factory](https://github.com/kerberos-io/agent/tree/master/deployments/factory).
|
||||
|
||||
## 6. Terraform
|
||||
|
||||
To be written
|
||||
|
||||
## 7. Salt
|
||||
|
||||
To be written
|
||||
49
deployments/ansible-openshift/README.md
Normal file
49
deployments/ansible-openshift/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Deploy to a Red Hat OpenShift cluster with Ansible
|
||||
|
||||
Kubernetes is great, but you might love OpenShift even more. In this directory you'll find some resources to deploy your Kerberos Agent in an OpenShift cluster using Ansible playbook. We'll review the different tasks of the Ansible playbook step by step; find the complete `playbook.yaml` here.
|
||||
|
||||
## Variabeles
|
||||
|
||||
We'll have a few `variables` in our `playbook.yml` that will help us to setup secure connection with the OpenShift cluster. We need the `cluster_url` and the `username` and `password` of the OpenShift cluster. If you don't know where to find this, you can find this in the OpenShift web ui.
|
||||
|
||||
vars:
|
||||
- oc_cluster_url: ""
|
||||
- oc_username: ""
|
||||
- oc_password: ""
|
||||
|
||||
## Tasks
|
||||
|
||||
Once we have supplied the `variables` we will define following tasks:
|
||||
|
||||
- name: Print Variables
|
||||
- name: Try to login to OCP cluster
|
||||
- name: Create a Namespace
|
||||
- name: Create a Persistent volume claim
|
||||
- name: Deploy Kerberos Agent
|
||||
- name: Expose Kerberos Agent
|
||||
|
||||
1. Print variables: this is a validation step, where we make sure we have the correct variables supplied to the `ansible-playbook` command. This confirms we are using the right credentials to setup a secure connection with the OpenShift cluster.
|
||||
|
||||
2. Setup a connection with OpenShift using the defined variabeles. If successfull an `api_key` will become available in the `k8s_auth_result` variable. This variabele will be used with every subsequent operation against the OpenShift cluster.
|
||||
|
||||
3. A best practice is to isolate your workloads in namespaces. Therefore we'll create a new namespace in our OpenShift cluster.
|
||||
|
||||
4. (Optional) Create a persistent volume to persist the configuration file and recordings in a volume.
|
||||
|
||||
5. Deploy Kerberos Agent through a `deployment`.
|
||||
|
||||
6. Expose the Kerberos Agent web interface through a `LoadBalancer`; public internet accessible IP address.
|
||||
|
||||
## Run the playbook
|
||||
|
||||
Now you understand what is happening in the playbook, let's run it. Make sure you have `ansible` install on your `host` or `deploy` machine.
|
||||
|
||||
Specify the `environment` input variable as a `JSON` with all required variables defined in step 1. Reference the `playbook.yml` file and execute.
|
||||
|
||||
ansible-playbook -e '{ \
|
||||
"oc_cluster_url":"https://api.j5z0adui.westeurope.aroapp.io:6443", \
|
||||
"oc_username":"kubeadmin",\
|
||||
"oc_password":"xxx" \
|
||||
}' playbook.yml
|
||||
|
||||
If everything runs as expected you should see you Kerberos Agent deployed, together with an assigned public ip address. Paste the ip address in your browser, the Kerberos Agent web interface will show up. You can use [the default username and password to sign-in](https://github.com/kerberos-io/agent#access-the-kerberos-agent), or if changed to your own (which is recommended).
|
||||
140
deployments/ansible-openshift/playbook.yml
Normal file
140
deployments/ansible-openshift/playbook.yml
Normal file
@@ -0,0 +1,140 @@
|
||||
- hosts: localhost
|
||||
vars:
|
||||
- oc_cluster_url: ""
|
||||
- oc_username: ""
|
||||
- oc_password: ""
|
||||
tasks:
|
||||
- name: Print Variables
|
||||
debug:
|
||||
msg: "OpenShift url: {{ oc_cluster_url }}, OpenShift username: {{ oc_username }}, OpenShift password: {{ oc_password }}"
|
||||
|
||||
- name: Try to login to OCP cluster
|
||||
k8s_auth:
|
||||
host: "{{ oc_cluster_url }}"
|
||||
username: "{{ oc_username }}"
|
||||
password: "{{ oc_password }}"
|
||||
validate_certs: no
|
||||
register: k8s_auth_result
|
||||
|
||||
- name: Create a Namespace
|
||||
k8s:
|
||||
state: present
|
||||
host: "{{ oc_cluster_url }}"
|
||||
api_key: "{{ k8s_auth_result.k8s_auth.api_key }}"
|
||||
validate_certs: no
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: kerberos
|
||||
register: k8s_namespace_result
|
||||
|
||||
- name: Create a Persistent volume claim
|
||||
k8s:
|
||||
state: present
|
||||
host: "{{ oc_cluster_url }}"
|
||||
api_key: "{{ k8s_auth_result.k8s_auth.api_key }}"
|
||||
validate_certs: no
|
||||
namespace: kerberos
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: kerberos-data
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
storageClassName: managed-premium
|
||||
register: pvc_result
|
||||
|
||||
- name: Deploy Kerberos Agent
|
||||
k8s:
|
||||
state: present
|
||||
apply: yes
|
||||
namespace: kerberos
|
||||
host: "{{ oc_cluster_url }}"
|
||||
api_key: "{{ k8s_auth_result.k8s_auth.api_key }}"
|
||||
validate_certs: no
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: agent
|
||||
labels:
|
||||
name: agent
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: agent
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: agent
|
||||
spec:
|
||||
volumes:
|
||||
- name: kerberos-data
|
||||
persistentVolumeClaim:
|
||||
claimName: kerberos-data
|
||||
|
||||
initContainers:
|
||||
- name: download-config
|
||||
image: kerberos/agent:1b96d01
|
||||
volumeMounts:
|
||||
- name: kerberos-data
|
||||
mountPath: /home/agent/data/config
|
||||
subPath: config
|
||||
command:
|
||||
[
|
||||
"cp",
|
||||
"/home/agent/data/config.template.json",
|
||||
"/home/agent/data/config/config.json",
|
||||
]
|
||||
|
||||
containers:
|
||||
- name: agent
|
||||
image: kerberos/agent:1b96d01
|
||||
volumeMounts:
|
||||
- name: kerberos-data
|
||||
mountPath: /home/agent/data/config
|
||||
subPath: config
|
||||
- name: kerberos-data
|
||||
mountPath: /home/agent/data/recordings
|
||||
subPath: recordings
|
||||
- name: kerberos-data
|
||||
mountPath: /home/agent/data/snapshots
|
||||
subPath: snapshots
|
||||
- name: kerberos-data
|
||||
mountPath: /home/agent/data/cloud
|
||||
subPath: cloud
|
||||
ports:
|
||||
- containerPort: 80
|
||||
protocol: TCP
|
||||
|
||||
- name: Expose Kerberos Agent
|
||||
k8s:
|
||||
state: present
|
||||
apply: yes
|
||||
namespace: kerberos
|
||||
host: "{{ oc_cluster_url }}"
|
||||
api_key: "{{ k8s_auth_result.k8s_auth.api_key }}"
|
||||
validate_certs: no
|
||||
definition:
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: agent-svc
|
||||
labels:
|
||||
name: agent-svc
|
||||
spec:
|
||||
selector:
|
||||
app: agent
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
92
deployments/docker/README.md
Normal file
92
deployments/docker/README.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Deployment with Docker
|
||||
|
||||
The easiest, and let's say most natural, deployment is done [by utilising `docker`](#1-running-a-single-container). Docker can run a stand-alone, single, Kerberos Agent (or container) and a bigger set of Kerberos Agents (or containers) [through `docker compose`](#2-running-multiple-containers-with-docker-compose).
|
||||
|
||||
## 1. Running a single container
|
||||
|
||||
We are creating Docker images as part of our CI/CD process. You'll find our Docker images on [Docker hub](https://hub.docker.com/r/kerberos/agent). Pick a specific tag of choice, or use latest. Once done run below command, this will open the web interface of your Kerberos agent on port 80.
|
||||
|
||||
docker run -p 80:80 --name mycamera -d kerberos/agent:latest
|
||||
|
||||
Or for a develop build:
|
||||
|
||||
docker run -p 80:80 --name mycamera -d kerberos/agent-dev:latest
|
||||
|
||||
Feel free to use another port if your host system already has a workload running on `80`. For example `8082`.
|
||||
|
||||
docker run -p 8082:80 --name mycamera -d kerberos/agent:latest
|
||||
|
||||
### Attach a volume
|
||||
|
||||
By default your Kerberos agent will store all its configuration and recordings inside the container. It might be interesting to store both configuration and your recordings outside the container, on your local disk. This helps persisting your storage even after you decide to wipe out your Kerberos agent.
|
||||
|
||||
You attach a volume to your container by leveraging the `-v` option. To mount your own configuration file, execute as following:
|
||||
|
||||
1. Decide where you would like to store your configuration and recordings; create a new directory for the config file and recordings folder accordingly.
|
||||
|
||||
mkdir agent
|
||||
mkdir agent/config
|
||||
mkdir agent/recordings
|
||||
|
||||
2. Once you have located your desired directory, copy the latest [`config.json`](https://github.com/kerberos-io/agent/blob/master/machinery/data/config/config.json) file into your config directory.
|
||||
|
||||
wget https://raw.githubusercontent.com/kerberos-io/agent/master/machinery/data/config/config.json -O agent/config/config.json
|
||||
|
||||
3. Run the docker command as following to attach your config directory and recording directory.
|
||||
|
||||
docker run -p 80:80 --name mycamera \
|
||||
-v $(pwd)/agent/config:/home/agent/data/config \
|
||||
-v $(pwd)/agent/recordings:/home/agent/data/recordings\
|
||||
-d --restart=alwayskerberos/agent:latest
|
||||
|
||||
### Override with environment variables
|
||||
|
||||
Next to attaching the configuration file, it is also possible to override the configuration with environment variables. This makes deployments easier when leveraging `docker compose` or `kubernetes` deployments much easier and scalable. Using this approach we simplify automation through `ansible` and `terraform`. You'll find [the full list of environment variables on the main README.md file](https://github.com/kerberos-io/agent#override-with-environment-variables).
|
||||
|
||||
### 2. Running multiple containers with Docker compose
|
||||
|
||||
When running multiple containers, you could execute the above process multiple times, or a better way is to run a `docker compose` with predefined configuration file, a `docker-compose.yaml`.
|
||||
|
||||
You'll find [an example `docker-compose.yaml` file here](https://github.com/kerberos-io/agent/blob/master/deployments/docker/docker-compose.yaml). This configuration file includes a definition for running 3 Kerberos Agents (or containers). By specifying environment variables you can override the internal configuration. To add more Kerberos Agents to your deployment, just `copy-paste` a `service` block and modify the name, exposed port, and settings accordingly.
|
||||
|
||||
kerberos-agent2:
|
||||
image: "kerberos/agent:latest"
|
||||
ports:
|
||||
- "8082:80"
|
||||
environment:
|
||||
- AGENT_NAME=agent2
|
||||
- AGENT_CAPTURE_IPCAMERA_RTSP=rtsp://x.x.x.x:554/Streaming/Channels/101
|
||||
- AGENT_HUB_KEY=yyy
|
||||
- AGENT_HUB_PRIVATE_KEY=yyy
|
||||
|
||||
#### Attaching volumes
|
||||
|
||||
As described in [1. Running a single container](#1-running-a-single-container) you can also assign volumes to your `docker compose` services. A volume can be added to persist the recordings of your Kerberos Agents on the host machine, or to provide more accurate configurations.
|
||||
|
||||
When attaching a volume for persisting recordings or mounting configuration files from the host system. the `docker-compose.yaml` would look like this.
|
||||
|
||||
Let's start by creating some directories on your host system. We'll consider 3 Kerberos Agents in this example.
|
||||
|
||||
mkdir -p agent1/config agent1/recordings
|
||||
mkdir -p agent2/config agent2/recordings
|
||||
mkdir -p agent3/config agent3/recordings
|
||||
|
||||
Download the configuration file in each Kerberos Agent configuration directory.
|
||||
|
||||
wget https://raw.githubusercontent.com/kerberos-io/agent/master/machinery/data/config/config.json -O agent1/config/config.json
|
||||
wget https://raw.githubusercontent.com/kerberos-io/agent/master/machinery/data/config/config.json -O agent2/config/config.json
|
||||
wget https://raw.githubusercontent.com/kerberos-io/agent/master/machinery/data/config/config.json -O agent3/config/config.json
|
||||
|
||||
Next we'll add a `volumes:` section to each Kerberos Agent (service) in the `docker-compose-with-volumes.yaml` file.
|
||||
|
||||
volumes:
|
||||
- ./agent1/config:/home/agent/data/config
|
||||
- ./agent1/recordings:/home/agent/data/recordings
|
||||
|
||||
We'll repeat that for the other Kerberos Agents as well. You can review [the final result over here](https://github.com/kerberos-io/agent/blob/master/deployments/docker/docker-compose-with-volumes.yaml).
|
||||
|
||||
Run the `docker compose` command by providing a different configuration file name.
|
||||
|
||||
docker compose -f docker-compose-with-volumes.yaml up
|
||||
|
||||
Please note that you can use a combination of using a configuration file and environment variables at the same time. However environment variables will always override the setting in your configuration file.
|
||||
27
deployments/docker/docker-compose-with-volumes.yaml
Normal file
27
deployments/docker/docker-compose-with-volumes.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
version: "3.9"
|
||||
services:
|
||||
kerberos-agent1:
|
||||
image: "kerberos/agent:latest"
|
||||
ports:
|
||||
- "8081:80"
|
||||
environment:
|
||||
- AGENT_NAME=agent1
|
||||
# You can still override the configuration with environment variables, but might not makes sense if you are attaching a host config.
|
||||
# find full list of environment variables here: https://github.com/kerberos-io/agent#override-with-environment-variables
|
||||
volumes:
|
||||
- ./agent1/config:/home/agent/data/config
|
||||
- ./agent1/recordings:/home/agent/data/recordings
|
||||
kerberos-agent2:
|
||||
image: "kerberos/agent:latest"
|
||||
ports:
|
||||
- "8082:80"
|
||||
volumes:
|
||||
- ./agent2/config:/home/agent/data/config
|
||||
- ./agent2/recordings:/home/agent/data/recordings
|
||||
kerberos-agent3:
|
||||
image: "kerberos/agent:latest"
|
||||
ports:
|
||||
- "8083:80"
|
||||
volumes:
|
||||
- ./agent3/config:/home/agent/data/config
|
||||
- ./agent3/recordings:/home/agent/data/recordings
|
||||
35
deployments/docker/docker-compose.yaml
Normal file
35
deployments/docker/docker-compose.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
version: "3.9"
|
||||
services:
|
||||
kerberos-agent1:
|
||||
image: "kerberos/agent:latest"
|
||||
ports:
|
||||
- "8081:80"
|
||||
environment:
|
||||
- AGENT_NAME=agent1
|
||||
- AGENT_CAPTURE_IPCAMERA_RTSP=rtsp://x.x.x.x:554/Streaming/Channels/101
|
||||
- AGENT_HUB_KEY=xxx
|
||||
- AGENT_HUB_PRIVATE_KEY=xxx
|
||||
- AGENT_CAPTURE_CONTINUOUS=true
|
||||
- AGENT_CAPTURE_PRERECORDING=10
|
||||
- AGENT_CAPTURE_POSTRECORDING=10
|
||||
- AGENT_CAPTURE_MAXLENGTH=60
|
||||
- AGENT_CAPTURE_PIXEL_CHANGE=150
|
||||
# find full list of environment variables here: https://github.com/kerberos-io/agent#override-with-environment-variables
|
||||
kerberos-agent2:
|
||||
image: "kerberos/agent:latest"
|
||||
ports:
|
||||
- "8082:80"
|
||||
environment:
|
||||
- AGENT_NAME=agent2
|
||||
- AGENT_CAPTURE_IPCAMERA_RTSP=rtsp://x.x.x.x:554/Streaming/Channels/101
|
||||
- AGENT_HUB_KEY=yyy
|
||||
- AGENT_HUB_PRIVATE_KEY=yyy
|
||||
kerberos-agent3:
|
||||
image: "kerberos/agent:latest"
|
||||
ports:
|
||||
- "8083:80"
|
||||
environment:
|
||||
- AGENT_NAME=agent3
|
||||
- AGENT_CAPTURE_IPCAMERA_RTSP=rtsp://x.x.x.x:554/Streaming/Channels/101
|
||||
- AGENT_HUB_KEY=zzz
|
||||
- AGENT_HUB_PRIVATE_KEY=zzz
|
||||
9
deployments/factory/README.md
Normal file
9
deployments/factory/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Deploy with Kerberos Factory
|
||||
|
||||
All of the previously deployments, `docker`, `kubernetes` and `openshift` are great for a technical audience. However for business users, it might be more convenient to have a clean web ui, that one can leverage to add one or more cameras (Kerberos Agents), without the hassle of the technical resources.
|
||||
|
||||
That's exactly why we have build [Kerberos Factory](https://github.com/kerberos-io/factory). It's a web ui on top of a Kubernetes cluster, which allows a non-technical users to administer and configure a video landscape.
|
||||
|
||||

|
||||
|
||||
The idea of [Kerberos Factory](https://github.com/kerberos-io/factory) is that one can configure and deploy a camera, by filling-in some basic input fields. Once done [Kerberos Factory](https://github.com/kerberos-io/factory), will create the relevant resources in your Kubernetes cluster.
|
||||
BIN
deployments/factory/factory-login.gif
Normal file
BIN
deployments/factory/factory-login.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
111
deployments/kubernetes/README.md
Normal file
111
deployments/kubernetes/README.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Deployment with Kubernetes
|
||||
|
||||
As described in the [Deployment with Docker](https://github.com/kerberos-io/agent/tree/master/deployments/docker), `docker` is a great tool for smaller deployments, where you are just running on a single machine and want to ramp up quickly. As you might expect, this is a not an ideal situation for production deployments.
|
||||
|
||||
Kubernetes can help you to build a scalable, flexible and resilient deployment. By introducing the concept of multi-nodes and deployments, you can make sure your Kerberos Agents are evenly distributed across your different machines, and you can add more nodes when you need to scale out.
|
||||
|
||||
We've provided an example deployment `deployment-agent.yml` in this directory, which show case you have to create a deployment (and under the hood a pod), to run a Kerberos Agent workload.
|
||||
|
||||
## Create a Kerberos Agent deployment
|
||||
|
||||
It's always a best practices to isolate and structure your workloads in Kubernetes. To achieve this we are utilising the concept of namespaces. For this example we will create a new namespace `demo`.
|
||||
|
||||
kubectl create namespace demo
|
||||
|
||||
Now we have a namespace, have a look at `deployment-agent.yml` in this folder. This configuration file describes the Kubernetes resources we would like to create, and how the Kerberos Agent needs to behave: environment variables, container ports, etc. At the bottom of the file, we find a `service` part, this tells Kubernetes to expose the Kerberos Agent user interface on a publicly accessible IP address. **_Please note that you don't need to expose this, as you can configure the Kerberos Agent with a volume and/or environment variables._**
|
||||
|
||||
Let's move on, and apply the Kerberos Agent deployment and service.
|
||||
|
||||
kubectl apply -f deployment-agent.yml -n demo
|
||||
|
||||
Watch deployment and service to be ready.
|
||||
|
||||
watch kubectl get all -n demo
|
||||
|
||||
When the deployment and service is created successfully, you should see something like this.
|
||||
|
||||
Every 2.0s: kubectl get all -n demo Fri Dec 9 16:33:17 2022
|
||||
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
pod/agent-7c75c4dbcf-zxrb5 1/1 Running 0 19s
|
||||
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
service/agent-svc LoadBalancer 10.x.x.x 108.x.x.x 80:32664/TCP 20s
|
||||
|
||||
NAME READY UP-TO-DATE AVAILABLE AGE
|
||||
deployment.apps/agent 1/1 1 1 20s
|
||||
|
||||
NAME DESIRED CURRENT READY AGE
|
||||
replicaset.apps/agent-7c75c4dbcf 1 1 1 20s
|
||||
|
||||
When copying the `EXTERNAL-IP` and pasting it in your browser, you should see the Kerberos Agent user interface. You can use [the default username and password to sign-in](https://github.com/kerberos-io/agent#access-the-kerberos-agent), or if changed to your own (which is recommended).
|
||||
|
||||
## Configure with volumes
|
||||
|
||||
Just like with `docker`, you can also attach `volumes` to the Kerberos Agent deployment, by creating a `Persistent Volume` and mount it to a specific directory.
|
||||
|
||||
Depending on where and how you are hosting the Kubernetes cluster, you may need to create a new `storageClass` or use a predefined `storageClass` from your cloud provider (Azure, GCP, AWS, ..). Have a look at `deployment-agent-volume.yml` to review a complete example.
|
||||
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: agent
|
||||
spec:
|
||||
volumes:
|
||||
- name: kerberos-data
|
||||
persistentVolumeClaim:
|
||||
claimName: kerberos-data
|
||||
...
|
||||
containers:
|
||||
- name: agent
|
||||
image: kerberos/agent:latest
|
||||
volumeMounts:
|
||||
- name: kerberos-data
|
||||
mountPath: /home/agent/data/config
|
||||
subPath: config
|
||||
...
|
||||
|
||||
## Expose with Ingress
|
||||
|
||||
In the first example `deployment-agent.yml` we are using a `LoadBalancer` to expose the Kerberos Agent user interface; as shown below. If you are a bit more experienced with Kubernetes, you will know there are other `service types` as well.
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
...
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- port: 80
|
||||
...
|
||||
|
||||
An alternative to `LoadBalancer` is `Ingress`. By leveraging an ingress such as `ingress-nginx` or `traefik` you setup a gateway (single point of contact), through which all communication to your apps (services) will flow.
|
||||
|
||||
A huge benefit (there are many others), is that you only allocate 1 public IP address for all your services. So instead of creating a `LoadBalancer` and thus a public IP address for every agent, you will create an `Ingress` service for each agent. Review the complete example at `deployment-agent-with-ingress.yml`.
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: agent-ingress
|
||||
labels:
|
||||
name: agent-ingress
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
kubernetes.io/tls-acme: "true"
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- "myagent.kerberos.io"
|
||||
secretName: agent-secret
|
||||
rules:
|
||||
- host: myagent.kerberos.io
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/"
|
||||
backend:
|
||||
service:
|
||||
name: agent-svc
|
||||
port:
|
||||
number: 80
|
||||
54
deployments/kubernetes/deployment-agent-volume.yml
Normal file
54
deployments/kubernetes/deployment-agent-volume.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: agent
|
||||
labels:
|
||||
name: agent
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: agent
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: agent
|
||||
spec:
|
||||
volumes:
|
||||
- name: kerberos-data
|
||||
persistentVolumeClaim:
|
||||
claimName: kerberos-data
|
||||
|
||||
initContainers:
|
||||
- name: download-config
|
||||
image: kerberos/agent:1b96d01
|
||||
volumeMounts:
|
||||
- name: kerberos-data
|
||||
mountPath: /home/agent/data/config
|
||||
subPath: config
|
||||
command:
|
||||
[
|
||||
"cp",
|
||||
"/home/agent/data/config.template.json",
|
||||
"/home/agent/data/config/config.json",
|
||||
]
|
||||
|
||||
containers:
|
||||
- name: agent
|
||||
image: kerberos/agent:latest
|
||||
volumeMounts:
|
||||
- name: kerberos-data
|
||||
mountPath: /home/agent/data/config
|
||||
subPath: config
|
||||
- name: kerberos-data
|
||||
mountPath: /home/agent/data/recordings
|
||||
subPath: recordings
|
||||
- name: kerberos-data
|
||||
mountPath: /home/agent/data/snapshots
|
||||
subPath: snapshots
|
||||
- name: kerberos-data
|
||||
mountPath: /home/agent/data/cloud
|
||||
subPath: cloud
|
||||
ports:
|
||||
- containerPort: 80
|
||||
protocol: TCP
|
||||
82
deployments/kubernetes/deployment-agent-with-ingress.yml
Normal file
82
deployments/kubernetes/deployment-agent-with-ingress.yml
Normal file
@@ -0,0 +1,82 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: agent
|
||||
labels:
|
||||
name: agent
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: agent
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: agent
|
||||
spec:
|
||||
containers:
|
||||
- name: agent
|
||||
image: kerberos/agent:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
protocol: TCP
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
env:
|
||||
- name: AGENT_NAME
|
||||
value: demo-agent
|
||||
- name: AGENT_CAPTURE_IPCAMERA_RTSP
|
||||
value: rtsp://fake.kerberos.io/stream
|
||||
- name: AGENT_HUB_KEY
|
||||
value: yyy
|
||||
- name: AGENT_HUB_PRIVATE_KEY
|
||||
value: yyy
|
||||
# find full list of environment variables here: https://github.com/kerberos-io/agent#override-with-environment-variables
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: agent-svc
|
||||
labels:
|
||||
name: agent-svc
|
||||
spec:
|
||||
#type: LoadBalancer
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: agent
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: agent-ingress
|
||||
labels:
|
||||
name: agent-ingress
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
kubernetes.io/tls-acme: "true"
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- "demo.kerberos.io"
|
||||
secretName: agent-secret
|
||||
rules:
|
||||
- host: demo.kerberos.io
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/"
|
||||
backend:
|
||||
service:
|
||||
name: agent-svc
|
||||
port:
|
||||
number: 80
|
||||
|
||||
53
deployments/kubernetes/deployment-agent.yml
Normal file
53
deployments/kubernetes/deployment-agent.yml
Normal file
@@ -0,0 +1,53 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: agent
|
||||
labels:
|
||||
name: agent
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: agent
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: agent
|
||||
spec:
|
||||
containers:
|
||||
- name: agent
|
||||
image: kerberos/agent:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
protocol: TCP
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
env:
|
||||
- name: AGENT_NAME
|
||||
value: demo-agent
|
||||
- name: AGENT_CAPTURE_IPCAMERA_RTSP
|
||||
value: rtsp://fake.kerberos.io/stream
|
||||
- name: AGENT_HUB_KEY
|
||||
value: yyy
|
||||
- name: AGENT_HUB_PRIVATE_KEY
|
||||
value: yyy
|
||||
# find full list of environment variables here: https://github.com/kerberos-io/agent#override-with-environment-variables
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: agent-svc
|
||||
labels:
|
||||
name: agent-svc
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: agent
|
||||
0
machinery/data/snapshots/.empty
Normal file
0
machinery/data/snapshots/.empty
Normal file
@@ -25,6 +25,162 @@ const docTemplate = `{
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/api/camera/onvif/capabilities": {
|
||||
"post": {
|
||||
"description": "Will return the ONVIF capabilities for the specific camera.",
|
||||
"tags": [
|
||||
"camera"
|
||||
],
|
||||
"summary": "Will return the ONVIF capabilities for the specific camera.",
|
||||
"operationId": "camera-onvif-capabilities",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "OnvifCredentials",
|
||||
"name": "config",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.OnvifCredentials"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/camera/onvif/login": {
|
||||
"post": {
|
||||
"description": "Try to login into ONVIF supported camera.",
|
||||
"tags": [
|
||||
"camera"
|
||||
],
|
||||
"summary": "Try to login into ONVIF supported camera.",
|
||||
"operationId": "camera-onvif-login",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "OnvifCredentials",
|
||||
"name": "config",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.OnvifCredentials"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/camera/onvif/pantilt": {
|
||||
"post": {
|
||||
"description": "Panning or/and tilting the camera using a direction (x,y).",
|
||||
"tags": [
|
||||
"camera"
|
||||
],
|
||||
"summary": "Panning or/and tilting the camera.",
|
||||
"operationId": "camera-onvif-pantilt",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "OnvifPanTilt",
|
||||
"name": "panTilt",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.OnvifPanTilt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/camera/onvif/zoom": {
|
||||
"post": {
|
||||
"description": "Zooming in or out the camera.",
|
||||
"tags": [
|
||||
"camera"
|
||||
],
|
||||
"summary": "Zooming in or out the camera.",
|
||||
"operationId": "camera-onvif-zoom",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "OnvifZoom",
|
||||
"name": "zoom",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.OnvifZoom"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/camera/verify/{streamType}": {
|
||||
"post": {
|
||||
"description": "This method will validate a specific profile connection from an RTSP camera, and try to get the codec.",
|
||||
"tags": [
|
||||
"camera"
|
||||
],
|
||||
"summary": "Validate a specific RTSP profile camera connection.",
|
||||
"operationId": "verify-camera",
|
||||
"parameters": [
|
||||
{
|
||||
"enum": [
|
||||
"primary",
|
||||
"secondary"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "Stream Type",
|
||||
"name": "streamType",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Camera Streams",
|
||||
"name": "cameraStreams",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.CameraStreams"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/hub/verify": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -59,6 +215,35 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/login": {
|
||||
"post": {
|
||||
"description": "Get Authorization token.",
|
||||
"tags": [
|
||||
"authentication"
|
||||
],
|
||||
"summary": "Get Authorization token.",
|
||||
"operationId": "login",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Credentials",
|
||||
"name": "credentials",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Authentication"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Authorization"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/persistence/verify": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -98,7 +283,50 @@ const docTemplate = `{
|
||||
"models.APIResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {}
|
||||
"data": {},
|
||||
"message": {}
|
||||
}
|
||||
},
|
||||
"models.Authentication": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Authorization": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"expire": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
},
|
||||
"token": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.CameraStreams": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rtsp": {
|
||||
"type": "string"
|
||||
},
|
||||
"sub_rtsp": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Capture": {
|
||||
@@ -151,6 +379,9 @@ const docTemplate = `{
|
||||
"models.Config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"auto_clean": {
|
||||
"type": "string"
|
||||
},
|
||||
"capture": {
|
||||
"$ref": "#/definitions/models.Capture"
|
||||
},
|
||||
@@ -182,6 +413,9 @@ const docTemplate = `{
|
||||
"kstorage": {
|
||||
"$ref": "#/definitions/models.KStorage"
|
||||
},
|
||||
"max_directory_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"mqtt_password": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -276,6 +510,7 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"cloud_key": {
|
||||
"description": "old way, remove this",
|
||||
"type": "string"
|
||||
},
|
||||
"directory": {
|
||||
@@ -292,6 +527,45 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.OnvifCredentials": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"onvif_password": {
|
||||
"type": "string"
|
||||
},
|
||||
"onvif_username": {
|
||||
"type": "string"
|
||||
},
|
||||
"onvif_xaddr": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.OnvifPanTilt": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"onvif_credentials": {
|
||||
"$ref": "#/definitions/models.OnvifCredentials"
|
||||
},
|
||||
"pan": {
|
||||
"type": "number"
|
||||
},
|
||||
"tilt": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.OnvifZoom": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"onvif_credentials": {
|
||||
"$ref": "#/definitions/models.OnvifCredentials"
|
||||
},
|
||||
"zoom": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Polygon": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -17,6 +17,162 @@
|
||||
},
|
||||
"basePath": "/",
|
||||
"paths": {
|
||||
"/api/camera/onvif/capabilities": {
|
||||
"post": {
|
||||
"description": "Will return the ONVIF capabilities for the specific camera.",
|
||||
"tags": [
|
||||
"camera"
|
||||
],
|
||||
"summary": "Will return the ONVIF capabilities for the specific camera.",
|
||||
"operationId": "camera-onvif-capabilities",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "OnvifCredentials",
|
||||
"name": "config",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.OnvifCredentials"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/camera/onvif/login": {
|
||||
"post": {
|
||||
"description": "Try to login into ONVIF supported camera.",
|
||||
"tags": [
|
||||
"camera"
|
||||
],
|
||||
"summary": "Try to login into ONVIF supported camera.",
|
||||
"operationId": "camera-onvif-login",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "OnvifCredentials",
|
||||
"name": "config",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.OnvifCredentials"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/camera/onvif/pantilt": {
|
||||
"post": {
|
||||
"description": "Panning or/and tilting the camera using a direction (x,y).",
|
||||
"tags": [
|
||||
"camera"
|
||||
],
|
||||
"summary": "Panning or/and tilting the camera.",
|
||||
"operationId": "camera-onvif-pantilt",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "OnvifPanTilt",
|
||||
"name": "panTilt",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.OnvifPanTilt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/camera/onvif/zoom": {
|
||||
"post": {
|
||||
"description": "Zooming in or out the camera.",
|
||||
"tags": [
|
||||
"camera"
|
||||
],
|
||||
"summary": "Zooming in or out the camera.",
|
||||
"operationId": "camera-onvif-zoom",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "OnvifZoom",
|
||||
"name": "zoom",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.OnvifZoom"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/camera/verify/{streamType}": {
|
||||
"post": {
|
||||
"description": "This method will validate a specific profile connection from an RTSP camera, and try to get the codec.",
|
||||
"tags": [
|
||||
"camera"
|
||||
],
|
||||
"summary": "Validate a specific RTSP profile camera connection.",
|
||||
"operationId": "verify-camera",
|
||||
"parameters": [
|
||||
{
|
||||
"enum": [
|
||||
"primary",
|
||||
"secondary"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "Stream Type",
|
||||
"name": "streamType",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Camera Streams",
|
||||
"name": "cameraStreams",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.CameraStreams"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/hub/verify": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -51,6 +207,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/login": {
|
||||
"post": {
|
||||
"description": "Get Authorization token.",
|
||||
"tags": [
|
||||
"authentication"
|
||||
],
|
||||
"summary": "Get Authorization token.",
|
||||
"operationId": "login",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Credentials",
|
||||
"name": "credentials",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Authentication"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Authorization"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/persistence/verify": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -90,7 +275,50 @@
|
||||
"models.APIResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {}
|
||||
"data": {},
|
||||
"message": {}
|
||||
}
|
||||
},
|
||||
"models.Authentication": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Authorization": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"expire": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
},
|
||||
"token": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.CameraStreams": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rtsp": {
|
||||
"type": "string"
|
||||
},
|
||||
"sub_rtsp": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Capture": {
|
||||
@@ -143,6 +371,9 @@
|
||||
"models.Config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"auto_clean": {
|
||||
"type": "string"
|
||||
},
|
||||
"capture": {
|
||||
"$ref": "#/definitions/models.Capture"
|
||||
},
|
||||
@@ -174,6 +405,9 @@
|
||||
"kstorage": {
|
||||
"$ref": "#/definitions/models.KStorage"
|
||||
},
|
||||
"max_directory_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"mqtt_password": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -268,6 +502,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"cloud_key": {
|
||||
"description": "old way, remove this",
|
||||
"type": "string"
|
||||
},
|
||||
"directory": {
|
||||
@@ -284,6 +519,45 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.OnvifCredentials": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"onvif_password": {
|
||||
"type": "string"
|
||||
},
|
||||
"onvif_username": {
|
||||
"type": "string"
|
||||
},
|
||||
"onvif_xaddr": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.OnvifPanTilt": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"onvif_credentials": {
|
||||
"$ref": "#/definitions/models.OnvifCredentials"
|
||||
},
|
||||
"pan": {
|
||||
"type": "number"
|
||||
},
|
||||
"tilt": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.OnvifZoom": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"onvif_credentials": {
|
||||
"$ref": "#/definitions/models.OnvifCredentials"
|
||||
},
|
||||
"zoom": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Polygon": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -3,6 +3,34 @@ definitions:
|
||||
models.APIResponse:
|
||||
properties:
|
||||
data: {}
|
||||
message: {}
|
||||
type: object
|
||||
models.Authentication:
|
||||
properties:
|
||||
password:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
models.Authorization:
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
expire:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
models.CameraStreams:
|
||||
properties:
|
||||
rtsp:
|
||||
type: string
|
||||
sub_rtsp:
|
||||
type: string
|
||||
type: object
|
||||
models.Capture:
|
||||
properties:
|
||||
@@ -37,6 +65,8 @@ definitions:
|
||||
type: object
|
||||
models.Config:
|
||||
properties:
|
||||
auto_clean:
|
||||
type: string
|
||||
capture:
|
||||
$ref: '#/definitions/models.Capture'
|
||||
cloud:
|
||||
@@ -58,6 +88,8 @@ definitions:
|
||||
type: string
|
||||
kstorage:
|
||||
$ref: '#/definitions/models.KStorage'
|
||||
max_directory_size:
|
||||
type: integer
|
||||
mqtt_password:
|
||||
type: string
|
||||
mqtt_username:
|
||||
@@ -120,6 +152,7 @@ definitions:
|
||||
access_key:
|
||||
type: string
|
||||
cloud_key:
|
||||
description: old way, remove this
|
||||
type: string
|
||||
directory:
|
||||
type: string
|
||||
@@ -130,6 +163,31 @@ definitions:
|
||||
uri:
|
||||
type: string
|
||||
type: object
|
||||
models.OnvifCredentials:
|
||||
properties:
|
||||
onvif_password:
|
||||
type: string
|
||||
onvif_username:
|
||||
type: string
|
||||
onvif_xaddr:
|
||||
type: string
|
||||
type: object
|
||||
models.OnvifPanTilt:
|
||||
properties:
|
||||
onvif_credentials:
|
||||
$ref: '#/definitions/models.OnvifCredentials'
|
||||
pan:
|
||||
type: number
|
||||
tilt:
|
||||
type: number
|
||||
type: object
|
||||
models.OnvifZoom:
|
||||
properties:
|
||||
onvif_credentials:
|
||||
$ref: '#/definitions/models.OnvifCredentials'
|
||||
zoom:
|
||||
type: number
|
||||
type: object
|
||||
models.Polygon:
|
||||
properties:
|
||||
coordinates:
|
||||
@@ -212,6 +270,110 @@ info:
|
||||
title: Swagger Kerberos Agent API
|
||||
version: "1.0"
|
||||
paths:
|
||||
/api/camera/onvif/capabilities:
|
||||
post:
|
||||
description: Will return the ONVIF capabilities for the specific camera.
|
||||
operationId: camera-onvif-capabilities
|
||||
parameters:
|
||||
- description: OnvifCredentials
|
||||
in: body
|
||||
name: config
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.OnvifCredentials'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIResponse'
|
||||
summary: Will return the ONVIF capabilities for the specific camera.
|
||||
tags:
|
||||
- camera
|
||||
/api/camera/onvif/login:
|
||||
post:
|
||||
description: Try to login into ONVIF supported camera.
|
||||
operationId: camera-onvif-login
|
||||
parameters:
|
||||
- description: OnvifCredentials
|
||||
in: body
|
||||
name: config
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.OnvifCredentials'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIResponse'
|
||||
summary: Try to login into ONVIF supported camera.
|
||||
tags:
|
||||
- camera
|
||||
/api/camera/onvif/pantilt:
|
||||
post:
|
||||
description: Panning or/and tilting the camera using a direction (x,y).
|
||||
operationId: camera-onvif-pantilt
|
||||
parameters:
|
||||
- description: OnvifPanTilt
|
||||
in: body
|
||||
name: panTilt
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.OnvifPanTilt'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIResponse'
|
||||
summary: Panning or/and tilting the camera.
|
||||
tags:
|
||||
- camera
|
||||
/api/camera/onvif/zoom:
|
||||
post:
|
||||
description: Zooming in or out the camera.
|
||||
operationId: camera-onvif-zoom
|
||||
parameters:
|
||||
- description: OnvifZoom
|
||||
in: body
|
||||
name: zoom
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.OnvifZoom'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIResponse'
|
||||
summary: Zooming in or out the camera.
|
||||
tags:
|
||||
- camera
|
||||
/api/camera/verify/{streamType}:
|
||||
post:
|
||||
description: This method will validate a specific profile connection from an
|
||||
RTSP camera, and try to get the codec.
|
||||
operationId: verify-camera
|
||||
parameters:
|
||||
- description: Stream Type
|
||||
enum:
|
||||
- primary
|
||||
- secondary
|
||||
in: path
|
||||
name: streamType
|
||||
required: true
|
||||
type: string
|
||||
- description: Camera Streams
|
||||
in: body
|
||||
name: cameraStreams
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.CameraStreams'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIResponse'
|
||||
summary: Validate a specific RTSP profile camera connection.
|
||||
tags:
|
||||
- camera
|
||||
/api/hub/verify:
|
||||
post:
|
||||
description: Will verify the hub connectivity.
|
||||
@@ -233,6 +395,25 @@ paths:
|
||||
summary: Will verify the hub connectivity.
|
||||
tags:
|
||||
- config
|
||||
/api/login:
|
||||
post:
|
||||
description: Get Authorization token.
|
||||
operationId: login
|
||||
parameters:
|
||||
- description: Credentials
|
||||
in: body
|
||||
name: credentials
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.Authentication'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Authorization'
|
||||
summary: Get Authorization token.
|
||||
tags:
|
||||
- authentication
|
||||
/api/persistence/verify:
|
||||
post:
|
||||
description: Will verify the persistence.
|
||||
|
||||
113
machinery/go.mod
113
machinery/go.mod
@@ -1,100 +1,131 @@
|
||||
module github.com/kerberos-io/agent/machinery
|
||||
|
||||
go 1.18
|
||||
go 1.19
|
||||
|
||||
//replace github.com/kerberos-io/joy4 v1.0.33 => ../../../../github.com/kerberos-io/joy4
|
||||
//replace github.com/kerberos-io/joy4 v1.0.48 => ../../../../github.com/kerberos-io/joy4
|
||||
//replace github.com/kerberos-io/onvif v0.0.5 => ../../../../github.com/kerberos-io/onvif
|
||||
|
||||
require (
|
||||
github.com/InVisionApp/conjungo v1.1.0
|
||||
github.com/appleboy/gin-jwt/v2 v2.8.0
|
||||
github.com/appleboy/gin-jwt/v2 v2.9.1
|
||||
github.com/cedricve/go-onvif v0.0.0-20200222191200-567e8ce298f6
|
||||
github.com/deepch/vdk v0.0.19
|
||||
github.com/eclipse/paho.mqtt.golang v1.4.1
|
||||
github.com/eclipse/paho.mqtt.golang v1.4.2
|
||||
github.com/elastic/go-sysinfo v1.9.0
|
||||
github.com/gin-contrib/cors v1.4.0
|
||||
github.com/gin-contrib/pprof v1.4.0
|
||||
github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/gin-gonic/contrib v0.0.0-20221130124618-7e01895a63f2
|
||||
github.com/gin-gonic/gin v1.8.2
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3
|
||||
github.com/golang-module/carbon/v2 v2.2.3
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/kellydunn/golang-geo v0.7.0
|
||||
github.com/kerberos-io/joy4 v1.0.33
|
||||
github.com/kerberos-io/onvif v0.0.3
|
||||
github.com/kerberos-io/joy4 v1.0.50
|
||||
github.com/kerberos-io/onvif v0.0.5
|
||||
github.com/minio/minio-go/v6 v6.0.57
|
||||
github.com/nsmith5/mjpeg v0.0.0-20200913181537-54b8ada0e53e
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pion/webrtc/v3 v3.1.43
|
||||
github.com/pion/webrtc/v3 v3.1.50
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
|
||||
github.com/swaggo/gin-swagger v1.5.2
|
||||
github.com/swaggo/swag v1.8.4
|
||||
github.com/swaggo/files v1.0.0
|
||||
github.com/swaggo/gin-swagger v1.5.3
|
||||
github.com/swaggo/swag v1.8.9
|
||||
github.com/tevino/abool v1.2.0
|
||||
gocv.io/x/gocv v0.31.0
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.46.0
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/DataDog/datadog-agent/pkg/obfuscate v0.0.0-20211129110424-6491aa3bf583 // indirect
|
||||
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.42.0-rc.1 // indirect
|
||||
github.com/DataDog/datadog-go v4.8.2+incompatible // indirect
|
||||
github.com/DataDog/datadog-go/v5 v5.0.2 // indirect
|
||||
github.com/DataDog/go-tuf v0.3.0--fix-localmeta-fork // indirect
|
||||
github.com/DataDog/gostackparse v0.5.0 // indirect
|
||||
github.com/DataDog/sketches-go v1.2.1 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/clbanning/mxj v1.8.4 // indirect
|
||||
github.com/elgs/gostrgen v0.0.0-20220325073726-0c3e00d082f6 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/elastic/go-windows v1.0.0 // indirect
|
||||
github.com/elgs/gostrgen v0.0.0-20161222160715-9d61ae07eeae // indirect
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/spec v0.20.6 // indirect
|
||||
github.com/go-openapi/swag v0.22.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.0 // indirect
|
||||
github.com/goccy/go-json v0.9.10 // indirect
|
||||
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.1 // indirect
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/gofrs/uuid v3.2.0+incompatible // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
||||
github.com/google/pprof v0.0.0-20210423192551-a2663126120b // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
|
||||
github.com/klauspost/cpuid v1.2.3 // indirect
|
||||
github.com/kylelemons/go-gypsy v1.0.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/lib/pq v1.10.6 // indirect
|
||||
github.com/lib/pq v1.10.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/minio/md5-simd v1.1.0 // indirect
|
||||
github.com/minio/sha256-simd v0.1.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
|
||||
github.com/pion/datachannel v1.5.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/philhofer/fwd v1.1.1 // indirect
|
||||
github.com/pion/datachannel v1.5.5 // indirect
|
||||
github.com/pion/dtls/v2 v2.1.5 // indirect
|
||||
github.com/pion/ice/v2 v2.2.7 // indirect
|
||||
github.com/pion/interceptor v0.1.12 // indirect
|
||||
github.com/pion/ice/v2 v2.2.12 // indirect
|
||||
github.com/pion/interceptor v0.1.11 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/mdns v0.0.5 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.10 // indirect
|
||||
github.com/pion/rtp v1.7.13 // indirect
|
||||
github.com/pion/sctp v1.8.2 // indirect
|
||||
github.com/pion/sctp v1.8.5 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.6 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.10 // indirect
|
||||
github.com/pion/stun v0.3.5 // indirect
|
||||
github.com/pion/transport v0.13.1 // indirect
|
||||
github.com/pion/transport v0.14.1 // indirect
|
||||
github.com/pion/turn/v2 v2.0.8 // indirect
|
||||
github.com/pion/udp v0.1.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||
github.com/tklauser/numcpus v0.5.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/tinylib/msgp v1.1.6 // indirect
|
||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
github.com/ziutek/mymysql v1.5.4 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
golang.org/x/net v0.4.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/grpc v1.32.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/ini.v1 v1.42.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
|
||||
inet.af/netaddr v0.0.0-20220617031823-097006376321 // indirect
|
||||
)
|
||||
|
||||
318
machinery/go.sum
318
machinery/go.sum
@@ -1,22 +1,52 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/datadog-agent/pkg/obfuscate v0.0.0-20211129110424-6491aa3bf583 h1:3nVO1nQyh64IUY6BPZUpMYMZ738Pu+LsMt3E0eqqIYw=
|
||||
github.com/DataDog/datadog-agent/pkg/obfuscate v0.0.0-20211129110424-6491aa3bf583/go.mod h1:EP9f4GqaDJyP1F5jTNMtzdIpw3JpNs3rMSJOnYywCiw=
|
||||
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.42.0-rc.1 h1:Rmz52Xlc5k3WzAHzD0SCH4USCzyti7EbK4HtrHys3ME=
|
||||
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.42.0-rc.1/go.mod h1:VVMDDibJxYEkwcLdZBT2g8EHKpbMT4JdOhRbQ9GdjbM=
|
||||
github.com/DataDog/datadog-go v4.8.2+incompatible h1:qbcKSx29aBLD+5QLvlQZlGmRMF/FfGqFLFev/1TDzRo=
|
||||
github.com/DataDog/datadog-go v4.8.2+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DataDog/datadog-go/v5 v5.0.2 h1:UFtEe7662/Qojxkw1d6SboAeA0CPI3naKhVASwFn+04=
|
||||
github.com/DataDog/datadog-go/v5 v5.0.2/go.mod h1:ZI9JFB4ewXbw1sBnF4sxsR2k1H3xjV+PUAOUsHvKpcU=
|
||||
github.com/DataDog/go-tuf v0.3.0--fix-localmeta-fork h1:yBq5PrAtrM4yVeSzQ+bn050+Ysp++RKF1QmtkL4VqvU=
|
||||
github.com/DataDog/go-tuf v0.3.0--fix-localmeta-fork/go.mod h1:yA5JwkZsHTLuqq3zaRgUQf35DfDkpOZqgtBqHKpwrBs=
|
||||
github.com/DataDog/gostackparse v0.5.0 h1:jb72P6GFHPHz2W0onsN51cS3FkaMDcjb0QzgxxA4gDk=
|
||||
github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
|
||||
github.com/DataDog/sketches-go v1.2.1 h1:qTBzWLnZ3kM2kw39ymh6rMcnN+5VULwFs++lEYUUsro=
|
||||
github.com/DataDog/sketches-go v1.2.1/go.mod h1:1xYmPLY1So10AwxV6MJV0J53XVH+WL9Ad1KetxVivVI=
|
||||
github.com/InVisionApp/conjungo v1.1.0 h1:NMPwvjK0i7UujnGnpbvAscruuroeYYVR55OAgBmM6oc=
|
||||
github.com/InVisionApp/conjungo v1.1.0/go.mod h1:86uJs2j/N0qcjh0ORLQDw1bB5kPFS/CoprqySSyp8dc=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
||||
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
|
||||
github.com/appleboy/gin-jwt/v2 v2.8.0 h1:Glo7cb9eBR+hj8Y7WzgfkOlqCaNLjP+RV4dNO3fpdps=
|
||||
github.com/appleboy/gin-jwt/v2 v2.8.0/go.mod h1:KsK7E8HTvRg3vOiumTsr/ntNTHbZ3IbHLe4Eto31p7k=
|
||||
github.com/appleboy/gin-jwt/v2 v2.9.1 h1:l29et8iLW6omcHltsOP6LLk4s3v4g2FbFs0koxGWVZs=
|
||||
github.com/appleboy/gin-jwt/v2 v2.9.1/go.mod h1:jwcPZJ92uoC9nOUTOKWoN/f6JZOgMSKlFSHw5/FrRUk=
|
||||
github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4=
|
||||
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
|
||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/cedricve/go-onvif v0.0.0-20200222191200-567e8ce298f6 h1:bzFZYgZD5vf4PWaa2GjOh90HG88uKi2a+B6VnQcDlCA=
|
||||
github.com/cedricve/go-onvif v0.0.0-20200222191200-567e8ce298f6/go.mod h1:nBrjN2nMHendp0Cvb/6GaJ1v92Qv/kzqxWtNBnKJEK0=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
|
||||
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -24,14 +54,27 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deepch/vdk v0.0.19 h1:r6xYyBTtXEIEh+csO0XHT00sI7xLF+hQFkJE9/go5II=
|
||||
github.com/deepch/vdk v0.0.19/go.mod h1:7ydHfSkflMZxBXfWR79dMjrT54xzvLxnPaByOa9Jpzg=
|
||||
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
|
||||
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eclipse/paho.mqtt.golang v1.4.1 h1:tUSpviiL5G3P9SZZJPC4ZULZJsxQKXxfENpMvdbAXAI=
|
||||
github.com/eclipse/paho.mqtt.golang v1.4.1/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6rNA5PM12m4=
|
||||
github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA=
|
||||
github.com/elastic/go-sysinfo v1.9.0 h1:usICqY/Nw4Mpn9f4LdtpFrKxXroJDe81GaxxUlCckIo=
|
||||
github.com/elastic/go-sysinfo v1.9.0/go.mod h1:eBD1wEGVaRnRLGecc9iG1z8eOv5HnEdz9+nWd8UAxcE=
|
||||
github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY=
|
||||
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
|
||||
github.com/elgs/gostrgen v0.0.0-20161222160715-9d61ae07eeae h1:3KvK2DmA7TxQ6PZ2f0rWbdqjgJhRcqgbY70bBeE4clI=
|
||||
github.com/elgs/gostrgen v0.0.0-20161222160715-9d61ae07eeae/go.mod h1:wruC5r2gHdr/JIUs5Rr1V45YtsAzKXZxAnn/5rPC97g=
|
||||
github.com/elgs/gostrgen v0.0.0-20220325073726-0c3e00d082f6 h1:x9TA+vnGEyqmWY+eA9HfgxNRkOQqwiEpFE9IPXSGuEA=
|
||||
github.com/elgs/gostrgen v0.0.0-20220325073726-0c3e00d082f6/go.mod h1:wruC5r2gHdr/JIUs5Rr1V45YtsAzKXZxAnn/5rPC97g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/flynn/go-docopt v0.0.0-20140912013429-f6dd2ebbb31e/go.mod h1:HyVoz1Mz5Co8TFO8EupIdlcpwShBmY98dkT2xeHkvEI=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
@@ -44,27 +87,24 @@ github.com/gin-contrib/pprof v1.4.0 h1:XxiBSf5jWZ5i16lNOPbMTVdgHBdhfGRD5PZ1LWazz
|
||||
github.com/gin-contrib/pprof v1.4.0/go.mod h1:RrehPJasUVBPK6yTUwOl8/NP6i0vbUgmxtis+Z5KE90=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19 h1:J2LPEOcQmWaooBnBtUDV9KHFEnP5LYTZY03GiQ0oQBw=
|
||||
github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg=
|
||||
github.com/gin-gonic/contrib v0.0.0-20221130124618-7e01895a63f2 h1:dyuNlYlG1faymw39NdJddnzJICy6587tiGSVioWhYoE=
|
||||
github.com/gin-gonic/contrib v0.0.0-20221130124618-7e01895a63f2/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg=
|
||||
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY=
|
||||
github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
|
||||
github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.22.0 h1:1VXunYCNgapcSzFtcY+eBmrwESlYCnFJZahQRgTRoo8=
|
||||
github.com/go-openapi/swag v0.22.0/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
@@ -74,21 +114,24 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
|
||||
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
||||
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc=
|
||||
github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-module/carbon/v2 v2.2.3 h1:WvGIc5+qzq9drNzH+Gnjh1TZ0JgDY/IA+m2Dvk7Qm4Q=
|
||||
github.com/golang-module/carbon/v2 v2.2.3/go.mod h1:LdzRApgmDT/wt0eNT8MEJbHfJdSqCtT46uZhfF30dqI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@@ -97,38 +140,49 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210423192551-a2663126120b h1:l2YRhr+YLzmSp7KJMswRVk/lO5SwoFIcCLzJsVj+YPc=
|
||||
github.com/google/pprof v0.0.0-20210423192551-a2663126120b/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hybridgroup/mjpeg v0.0.0-20140228234708-4680f319790e/go.mod h1:eagM805MRKrioHYuU7iKLUyFPVKqVV6um5DAvCkUtXs=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kellydunn/golang-geo v0.7.0 h1:A5j0/BvNgGwY6Yb6inXQxzYwlPHc6WVZR+MrarZYNNg=
|
||||
github.com/kellydunn/golang-geo v0.7.0/go.mod h1:YYlQPJ+DPEzrHx8kT3oPHC/NjyvCCXE+IuKGKdrjrcU=
|
||||
github.com/kerberos-io/joy4 v1.0.33 h1:JSIZMm7sWU9Ir7DykaSnySz4tzQMTtVgiUrp3uKWyvc=
|
||||
github.com/kerberos-io/joy4 v1.0.33/go.mod h1:nZp4AjvKvTOXRrmDyAIOw+Da+JA5OcSo/JundGfOlFU=
|
||||
github.com/kerberos-io/onvif v0.0.3 h1:Jcc6kKO2hyhaBddQyXyKAhqkrzeX53UH6WKoASXSBoc=
|
||||
github.com/kerberos-io/onvif v0.0.3/go.mod h1:Hr2dJOH2LM5SpYKk17gYZ1CMjhGhUl+QlT5kwYogrW0=
|
||||
github.com/kerberos-io/joy4 v1.0.50 h1:N1qr0Q6ytZPG5ZmG1hDVXWeRQ7jzM7f5QftDQ/KQVCo=
|
||||
github.com/kerberos-io/joy4 v1.0.50/go.mod h1:nZp4AjvKvTOXRrmDyAIOw+Da+JA5OcSo/JundGfOlFU=
|
||||
github.com/kerberos-io/onvif v0.0.5 h1:kq9mnHZkih9Jl4DyIJ4Rzt++Y3DDKy3nI8S2ESEfZ5w=
|
||||
github.com/kerberos-io/onvif v0.0.5/go.mod h1:Hr2dJOH2LM5SpYKk17gYZ1CMjhGhUl+QlT5kwYogrW0=
|
||||
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
|
||||
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
@@ -143,24 +197,23 @@ github.com/kylelemons/go-gypsy v1.0.0/go.mod h1:chkXM0zjdpXOiqkCW1XcCHDfjfk14PH2
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
|
||||
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
|
||||
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v6 v6.0.57 h1:ixPkbKkyD7IhnluRgQpGSpHdpvNVaW6OD5R9IAO/9Tw=
|
||||
github.com/minio/minio-go/v6 v6.0.57/go.mod h1:5+R/nM9Pwrh0vqF+HbYYDQ84wdUFPyXHkrdT4AIkifM=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
|
||||
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -177,35 +230,37 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
|
||||
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
|
||||
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
|
||||
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
|
||||
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||
github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
|
||||
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
|
||||
github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE=
|
||||
github.com/pion/ice/v2 v2.2.7 h1:kG9tux3WdYUSqqqnf+O5zKlpy41PdlvLUBlYJeV2emQ=
|
||||
github.com/pion/ice/v2 v2.2.7/go.mod h1:Ckj7cWZ717rtU01YoDQA9ntGWCk95D42uVZ8sI0EL+8=
|
||||
github.com/pion/ice/v2 v2.2.12 h1:n3M3lUMKQM5IoofhJo73D3qVla+mJN2nVvbSPq32Nig=
|
||||
github.com/pion/ice/v2 v2.2.12/go.mod h1:z2KXVFyRkmjetRlaVRgjO9U3ShKwzhlUylvxKfHfd5A=
|
||||
github.com/pion/interceptor v0.1.11 h1:00U6OlqxA3FFB50HSg25J/8cWi7P6FbSzw4eFn24Bvs=
|
||||
github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8=
|
||||
github.com/pion/interceptor v0.1.12 h1:CslaNriCFUItiXS5o+hh5lpL0t0ytQkFnUcbbCs2Zq8=
|
||||
github.com/pion/interceptor v0.1.12/go.mod h1:bDtgAD9dRkBZpWHGKaoKb42FhDHTG2rX8Ii9LRALLVA=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
||||
@@ -217,10 +272,8 @@ github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
||||
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA=
|
||||
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||
github.com/pion/sctp v1.8.5 h1:JCc25nghnXWOlSn3OVtEnA9PjQ2JsxQbG+CXZ1UkJKQ=
|
||||
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
||||
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||
github.com/pion/srtp/v2 v2.0.10 h1:b8ZvEuI+mrL8hbr/f1YiJFB34UMrOac3R3N1yq2UN0w=
|
||||
@@ -228,35 +281,53 @@ github.com/pion/srtp/v2 v2.0.10/go.mod h1:XEeSWaK9PfuMs7zxXyiN252AHPbH12NX5q/CFD
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||
github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
|
||||
github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
|
||||
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
||||
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pion/webrtc/v3 v3.1.43 h1:YT3ZTO94UT4kSBvZnRAH82+0jJPUruiKr9CEstdlQzk=
|
||||
github.com/pion/webrtc/v3 v3.1.43/go.mod h1:G/J8k0+grVsjC/rjCZ24AKoCCxcFFODgh7zThNZGs0M=
|
||||
github.com/pion/webrtc/v3 v3.1.50 h1:wLMo1+re4WMZ9Kun9qcGcY+XoHkE3i0CXrrc0sjhVCk=
|
||||
github.com/pion/webrtc/v3 v3.1.50/go.mod h1:y9n09weIXB+sjb9mi0GBBewNxo4TKUQm5qdtT5v3/X4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 h1:Qp27Idfgi6ACvFQat5+VJvlYToylpM/hcyLBI3WaKPA=
|
||||
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052/go.mod h1:uvX/8buq8uVeiZiFht+0lqSLBHF+uGV8BrTv8W/SIwk=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.3.1/go.mod h1:o8hhjkbNl2gOamKUA/eNW3xUrntHT9L4W89W1nfj43U=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0/go.mod h1:FGBZgq2tXWICsxWQW1msNf49F0Pf2Op5Htayx335Qbs=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@@ -264,30 +335,28 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||
github.com/swaggo/gin-swagger v1.5.2 h1:dj2es17EaOHoy0Owu4xn3An1mI8/xjdFyIH6KAbOdYo=
|
||||
github.com/swaggo/gin-swagger v1.5.2/go.mod h1:Cbj/MlHApPOjZdf4joWFXLLgmZVPyh54GPvPPyVjVZM=
|
||||
github.com/swaggo/files v1.0.0 h1:1gGXVIeUFCS/dta17rnP0iOpr6CXFwKD7EO5ID233e4=
|
||||
github.com/swaggo/files v1.0.0/go.mod h1:N59U6URJLyU1PQgFqPM7wXLMhJx7QAolnvfQkqO13kc=
|
||||
github.com/swaggo/gin-swagger v1.5.3 h1:8mWmHLolIbrhJJTflsaFoZzRBYVmEE7JZGIq08EiC0Q=
|
||||
github.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89Yp4uA50HpI=
|
||||
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
|
||||
github.com/swaggo/swag v1.8.4 h1:oGB351qH1JqUqK1tsMYEE5qTBbPk394BhsZxmUfebcI=
|
||||
github.com/swaggo/swag v1.8.4/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
|
||||
github.com/swaggo/swag v1.8.9 h1:kHtaBe/Ob9AZzAANfcn5c6RyCke9gG9QpH0jky0I/sA=
|
||||
github.com/swaggo/swag v1.8.9/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
|
||||
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
||||
github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo=
|
||||
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
|
||||
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
|
||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A=
|
||||
github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo=
|
||||
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
|
||||
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
@@ -296,33 +365,47 @@ github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
gocv.io/x/gocv v0.31.0 h1:BHDtK8v+YPvoSPQTTiZB2fM/7BLg6511JqkruY2z6LQ=
|
||||
gocv.io/x/gocv v0.31.0/go.mod h1:oc6FvfYqfBp99p+yOEzs9tbYF9gOrAQSeL/dyIPefJU=
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
@@ -333,18 +416,23 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -352,12 +440,18 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -365,27 +459,41 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
@@ -394,6 +502,16 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200726014623-da3ae01ef02d h1:HJaAqDnKreMkv+AQyf1Mcw0jEmL9kKBNL07RDJu1N/k=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -402,9 +520,12 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.46.0 h1:h/SbNfGfDMhBkB+/zzCWKPOlLcdd0Fc+QBAnZm009XM=
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.46.0/go.mod h1:kaa8caaECrtY0V/MUtPQAh1lx/euFzPJwrY1taTx3O4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -413,15 +534,15 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -434,3 +555,10 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
inet.af/netaddr v0.0.0-20220617031823-097006376321 h1:B4dC8ySKTQXasnjDTMsoCMf1sQG4WsMej0WXaHxunmU=
|
||||
inet.af/netaddr v0.0.0-20220617031823-097006376321/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
|
||||
|
||||
@@ -5,17 +5,51 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/kerberos-io/agent/machinery/src/capture"
|
||||
"github.com/kerberos-io/agent/machinery/src/components"
|
||||
"github.com/kerberos-io/agent/machinery/src/log"
|
||||
"github.com/kerberos-io/agent/machinery/src/models"
|
||||
"github.com/kerberos-io/agent/machinery/src/routers"
|
||||
"github.com/kerberos-io/agent/machinery/src/utils"
|
||||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
||||
"gopkg.in/DataDog/dd-trace-go.v1/profiler"
|
||||
)
|
||||
|
||||
var VERSION = "3.0.0"
|
||||
|
||||
func main() {
|
||||
|
||||
const VERSION = "3.0"
|
||||
// You might be interested in debugging the agent.
|
||||
if os.Getenv("DATADOG_AGENT_ENABLED") == "true" {
|
||||
if os.Getenv("DATADOG_AGENT_K8S_ENABLED") == "true" {
|
||||
tracer.Start()
|
||||
defer tracer.Stop()
|
||||
} else {
|
||||
service := os.Getenv("DATADOG_AGENT_SERVICE")
|
||||
environment := os.Getenv("DATADOG_AGENT_ENVIRONMENT")
|
||||
fmt.Println("Starting Datadog Agent with service: " + service + " and environment: " + environment)
|
||||
rules := []tracer.SamplingRule{tracer.RateRule(1)}
|
||||
tracer.Start(
|
||||
tracer.WithSamplingRules(rules),
|
||||
tracer.WithService(service),
|
||||
tracer.WithEnv(environment),
|
||||
)
|
||||
defer tracer.Stop()
|
||||
err := profiler.Start(
|
||||
profiler.WithService(service),
|
||||
profiler.WithEnv(environment),
|
||||
profiler.WithProfileTypes(
|
||||
profiler.CPUProfile,
|
||||
profiler.HeapProfile,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
log.Log.Fatal(err.Error())
|
||||
}
|
||||
defer profiler.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Start the show ;)
|
||||
action := os.Args[1]
|
||||
|
||||
timezone, _ := time.LoadLocation("CET")
|
||||
@@ -34,16 +68,18 @@ func main() {
|
||||
timeout := os.Args[2]
|
||||
fmt.Println(timeout)
|
||||
|
||||
case "usbcamera-test":
|
||||
|
||||
deviceID := os.Args[2]
|
||||
capture.TestUSBCamera(deviceID)
|
||||
|
||||
case "run":
|
||||
{
|
||||
name := os.Args[2]
|
||||
port := os.Args[3]
|
||||
|
||||
// Check the folder permissions, it might be that we do not have permissions to write
|
||||
// recordings, update the configuration or save snapshots.
|
||||
err := utils.CheckDataDirectoryPermissions()
|
||||
if err != nil {
|
||||
log.Log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// Read the config on start, and pass it to the other
|
||||
// function and features. Please note that this might be changed
|
||||
// when saving or updating the configuration through the REST api or MQTT handler.
|
||||
@@ -54,6 +90,10 @@ func main() {
|
||||
// Open this configuration either from Kerberos Agent or Kerberos Factory.
|
||||
components.OpenConfig(&configuration)
|
||||
|
||||
// We will override the configuration with the environment variables
|
||||
components.OverrideWithEnvironmentVariables(&configuration)
|
||||
|
||||
// Set timezone
|
||||
timezone, _ := time.LoadLocation(configuration.Config.Timezone)
|
||||
log.Log.Init(timezone)
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@ func HandleStream(infile av.DemuxCloser, queue *pubsub.Queue, communication *mod
|
||||
var err error
|
||||
loop:
|
||||
for {
|
||||
|
||||
// This will check if we need to stop the thread,
|
||||
// because of a reconfiguration.
|
||||
select {
|
||||
@@ -95,3 +94,42 @@ loop:
|
||||
queue.Close()
|
||||
log.Log.Debug("HandleStream: finished")
|
||||
}
|
||||
|
||||
func HandleSubStream(infile av.DemuxCloser, queue *pubsub.Queue, communication *models.Communication) { //, wg *sync.WaitGroup) {
|
||||
|
||||
log.Log.Debug("HandleSubStream: started")
|
||||
var err error
|
||||
loop:
|
||||
for {
|
||||
// This will check if we need to stop the thread,
|
||||
// because of a reconfiguration.
|
||||
select {
|
||||
case <-communication.HandleSubStream:
|
||||
break loop
|
||||
default:
|
||||
}
|
||||
|
||||
var pkt av.Packet
|
||||
if pkt, err = infile.ReadPacket(); err != nil { // sometimes this throws an end of file..
|
||||
log.Log.Error("HandleSubStream: " + err.Error())
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
// Could be that a decode is throwing errors.
|
||||
if len(pkt.Data) > 0 {
|
||||
|
||||
queue.WritePacket(pkt)
|
||||
|
||||
// This will check if we need to stop the thread,
|
||||
// because of a reconfiguration.
|
||||
select {
|
||||
case <-communication.HandleSubStream:
|
||||
break loop
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queue.Close()
|
||||
log.Log.Debug("HandleSubStream: finished")
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/kerberos-io/agent/machinery/src/log"
|
||||
"gocv.io/x/gocv"
|
||||
)
|
||||
|
||||
func TestUSBCamera(deviceID string) {
|
||||
webcam, err := gocv.OpenVideoCapture(deviceID)
|
||||
if err != nil {
|
||||
log.Log.Error("Error opening video capture device: " + deviceID)
|
||||
return
|
||||
}
|
||||
defer webcam.Close()
|
||||
buf := gocv.NewMat()
|
||||
defer buf.Close()
|
||||
|
||||
ok := webcam.Read(&buf)
|
||||
|
||||
if ok {
|
||||
|
||||
now := time.Now().Unix()
|
||||
saveFile := "./data/capture-test/" + strconv.FormatInt(now, 10) + ".mp4"
|
||||
fps := 20.0 // webcam.Get(gocv.VideoCaptureFPS)
|
||||
writer, err := gocv.VideoWriterFile(saveFile, "X264", fps, buf.Cols(), buf.Rows(), true)
|
||||
if err != nil {
|
||||
log.Log.Error("error opening video writer device: " + saveFile)
|
||||
return
|
||||
}
|
||||
defer writer.Close()
|
||||
|
||||
//window := gocv.NewWindow("Hello")
|
||||
log.Log.Info("Start reading device: " + deviceID)
|
||||
for i := 0; i < 100; i++ {
|
||||
if ok := webcam.Read(&buf); !ok {
|
||||
log.Log.Error("Device closed: " + deviceID)
|
||||
return
|
||||
}
|
||||
if buf.Empty() {
|
||||
continue
|
||||
}
|
||||
|
||||
writer.Write(buf)
|
||||
//window.IMShow(buf)
|
||||
//window.WaitKey(1)
|
||||
|
||||
log.Log.Info("Read frame")
|
||||
}
|
||||
|
||||
}
|
||||
log.Log.Info("Done. Close videocapture and recording file.")
|
||||
}
|
||||
@@ -3,11 +3,10 @@ package capture
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/kerberos-io/agent/machinery/src/log"
|
||||
"github.com/kerberos-io/agent/machinery/src/models"
|
||||
"github.com/kerberos-io/agent/machinery/src/utils"
|
||||
@@ -22,7 +21,7 @@ func CleanupRecordingDirectory(configuration *models.Configuration) {
|
||||
if autoClean == "true" {
|
||||
maxSize := configuration.Config.MaxDirectorySize
|
||||
if maxSize == 0 {
|
||||
maxSize = 50
|
||||
maxSize = 300
|
||||
}
|
||||
// Total size of the recording directory.
|
||||
recordingsDirectory := "./data/recordings"
|
||||
@@ -51,112 +50,233 @@ func CleanupRecordingDirectory(configuration *models.Configuration) {
|
||||
}
|
||||
}
|
||||
|
||||
func HandleRecordStream(recordingCursor *pubsub.QueueCursor, configuration *models.Configuration, communication *models.Communication, streams []av.CodecData) {
|
||||
log.Log.Debug("HandleRecordStream: started")
|
||||
func HandleRecordStream(queue *pubsub.Queue, configuration *models.Configuration, communication *models.Communication, streams []av.CodecData) {
|
||||
|
||||
config := configuration.Config
|
||||
recordingPeriod := config.Capture.PostRecording // number of seconds to record.
|
||||
maxRecordingPeriod := config.Capture.MaxLengthRecording // maximum number of seconds to record.
|
||||
|
||||
// Synchronise the last synced time
|
||||
now := time.Now().Unix()
|
||||
startRecording := now
|
||||
timestamp := now
|
||||
if config.Capture.Recording == "false" {
|
||||
log.Log.Info("HandleRecordStream: disabled, we will not record anything.")
|
||||
} else {
|
||||
log.Log.Debug("HandleRecordStream: started")
|
||||
|
||||
// Check if continuous recording.
|
||||
if config.Capture.Continuous == "true" {
|
||||
recordingPeriod := config.Capture.PostRecording // number of seconds to record.
|
||||
maxRecordingPeriod := config.Capture.MaxLengthRecording // maximum number of seconds to record.
|
||||
|
||||
// Do not do anything!
|
||||
log.Log.Info("HandleRecordStream: Start continuous recording ")
|
||||
// Synchronise the last synced time
|
||||
now := time.Now().Unix()
|
||||
startRecording := now
|
||||
timestamp := now
|
||||
|
||||
loc, _ := time.LoadLocation(config.Timezone)
|
||||
now = time.Now().Unix()
|
||||
timestamp = now
|
||||
start := false
|
||||
var name string
|
||||
var myMuxer *mp4.Muxer
|
||||
var file *os.File
|
||||
var err error
|
||||
// Check if continuous recording.
|
||||
if config.Capture.Continuous == "true" {
|
||||
|
||||
// If continuous record the full length
|
||||
recordingPeriod = maxRecordingPeriod
|
||||
// Recording file name
|
||||
fullName := ""
|
||||
// Do not do anything!
|
||||
log.Log.Info("HandleRecordStream: Start continuous recording ")
|
||||
|
||||
// Get as much packets we need.
|
||||
//for pkt := range packets {
|
||||
var cursorError error
|
||||
var pkt av.Packet
|
||||
recordingStatus := "idle"
|
||||
loc, _ := time.LoadLocation(config.Timezone)
|
||||
now = time.Now().Unix()
|
||||
timestamp = now
|
||||
start := false
|
||||
var name string
|
||||
var myMuxer *mp4.Muxer
|
||||
var file *os.File
|
||||
var err error
|
||||
|
||||
for cursorError == nil {
|
||||
// If continuous record the full length
|
||||
recordingPeriod = maxRecordingPeriod
|
||||
// Recording file name
|
||||
fullName := ""
|
||||
|
||||
pkt, cursorError = recordingCursor.ReadPacket()
|
||||
// Get as much packets we need.
|
||||
//for pkt := range packets {
|
||||
var cursorError error
|
||||
var pkt av.Packet
|
||||
var nextPkt av.Packet
|
||||
recordingStatus := "idle"
|
||||
recordingCursor := queue.Oldest()
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
if start && // If already recording and current frame is a keyframe and we should stop recording
|
||||
pkt.IsKeyFrame && (timestamp+recordingPeriod-now <= 0 || now-startRecording >= maxRecordingPeriod) {
|
||||
|
||||
// This will write the trailer a well.
|
||||
if err := myMuxer.WriteTrailer(); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
|
||||
log.Log.Info("HandleRecordStream: Recording finished: file save: " + name)
|
||||
file.Close()
|
||||
|
||||
// Check if need to convert to fragmented using bento
|
||||
if config.Capture.Fragmented == "true" && config.Capture.FragmentedDuration > 0 {
|
||||
utils.CreateFragmentedMP4(fullName, config.Capture.FragmentedDuration)
|
||||
}
|
||||
|
||||
// Create a symbol link.
|
||||
fc, _ := os.Create("./data/cloud/" + name)
|
||||
fc.Close()
|
||||
|
||||
// Cleanup muxer
|
||||
start = false
|
||||
myMuxer = nil
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
|
||||
recordingStatus = "idle"
|
||||
|
||||
// Clean up the recording directory if necessary.
|
||||
CleanupRecordingDirectory(configuration)
|
||||
if cursorError == nil {
|
||||
pkt, cursorError = recordingCursor.ReadPacket()
|
||||
}
|
||||
|
||||
// If not yet started and a keyframe, let's make a recording
|
||||
if !start && pkt.IsKeyFrame {
|
||||
for cursorError == nil {
|
||||
|
||||
// Check if within time interval
|
||||
nowInTimezone := time.Now().In(loc)
|
||||
weekday := nowInTimezone.Weekday()
|
||||
hour := nowInTimezone.Hour()
|
||||
minute := nowInTimezone.Minute()
|
||||
second := nowInTimezone.Second()
|
||||
timeEnabled := config.Time
|
||||
timeInterval := config.Timetable[int(weekday)]
|
||||
nextPkt, cursorError = recordingCursor.ReadPacket()
|
||||
|
||||
if timeEnabled == "true" && timeInterval != nil {
|
||||
start1 := timeInterval.Start1
|
||||
end1 := timeInterval.End1
|
||||
start2 := timeInterval.Start2
|
||||
end2 := timeInterval.End2
|
||||
currentTimeInSeconds := hour*60*60 + minute*60 + second
|
||||
if (currentTimeInSeconds >= start1 && currentTimeInSeconds <= end1) ||
|
||||
(currentTimeInSeconds >= start2 && currentTimeInSeconds <= end2) {
|
||||
now := time.Now().Unix()
|
||||
|
||||
} else {
|
||||
log.Log.Debug("HandleRecordStream: Disabled: no continuous recording at this moment. Not within specified time interval.")
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
if start && // If already recording and current frame is a keyframe and we should stop recording
|
||||
nextPkt.IsKeyFrame && (timestamp+recordingPeriod-now <= 0 || now-startRecording >= maxRecordingPeriod) {
|
||||
|
||||
// Write the last packet
|
||||
if err := myMuxer.WritePacket(pkt); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
|
||||
// This will write the trailer a well.
|
||||
if err := myMuxer.WriteTrailerWithPacket(nextPkt); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
|
||||
log.Log.Info("HandleRecordStream: Recording finished: file save: " + name)
|
||||
|
||||
// Cleanup muxer
|
||||
start = false
|
||||
myMuxer.Close()
|
||||
myMuxer = nil
|
||||
file.Close()
|
||||
file = nil
|
||||
|
||||
// Check if need to convert to fragmented using bento
|
||||
if config.Capture.Fragmented == "true" && config.Capture.FragmentedDuration > 0 {
|
||||
utils.CreateFragmentedMP4(fullName, config.Capture.FragmentedDuration)
|
||||
}
|
||||
|
||||
// Create a symbol link.
|
||||
fc, _ := os.Create("./data/cloud/" + name)
|
||||
fc.Close()
|
||||
|
||||
recordingStatus = "idle"
|
||||
|
||||
// Clean up the recording directory if necessary.
|
||||
CleanupRecordingDirectory(configuration)
|
||||
}
|
||||
|
||||
// If not yet started and a keyframe, let's make a recording
|
||||
if !start && pkt.IsKeyFrame {
|
||||
|
||||
// Check if within time interval
|
||||
nowInTimezone := time.Now().In(loc)
|
||||
weekday := nowInTimezone.Weekday()
|
||||
hour := nowInTimezone.Hour()
|
||||
minute := nowInTimezone.Minute()
|
||||
second := nowInTimezone.Second()
|
||||
timeEnabled := config.Time
|
||||
timeInterval := config.Timetable[int(weekday)]
|
||||
|
||||
if timeEnabled == "true" && timeInterval != nil {
|
||||
start1 := timeInterval.Start1
|
||||
end1 := timeInterval.End1
|
||||
start2 := timeInterval.Start2
|
||||
end2 := timeInterval.End2
|
||||
currentTimeInSeconds := hour*60*60 + minute*60 + second
|
||||
if (currentTimeInSeconds >= start1 && currentTimeInSeconds <= end1) ||
|
||||
(currentTimeInSeconds >= start2 && currentTimeInSeconds <= end2) {
|
||||
|
||||
} else {
|
||||
log.Log.Debug("HandleRecordStream: Disabled: no continuous recording at this moment. Not within specified time interval.")
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
start = true
|
||||
timestamp = now
|
||||
|
||||
// timestamp_microseconds_instanceName_regionCoordinates_numberOfChanges_token
|
||||
// 1564859471_6-474162_oprit_577-283-727-375_1153_27.mp4
|
||||
// - Timestamp
|
||||
// - Size + - + microseconds
|
||||
// - device
|
||||
// - Region
|
||||
// - Number of changes
|
||||
// - Token
|
||||
|
||||
startRecording = time.Now().Unix() // we mark the current time when the record started.ss
|
||||
s := strconv.FormatInt(startRecording, 10) + "_" +
|
||||
"6" + "-" +
|
||||
"967003" + "_" +
|
||||
config.Name + "_" +
|
||||
"200-200-400-400" + "_0_" +
|
||||
"769"
|
||||
|
||||
name = s + ".mp4"
|
||||
fullName = "./data/recordings/" + name
|
||||
|
||||
// Running...
|
||||
log.Log.Info("Recording started")
|
||||
|
||||
file, err = os.Create(fullName)
|
||||
if err == nil {
|
||||
myMuxer = mp4.NewMuxer(file)
|
||||
}
|
||||
|
||||
log.Log.Info("HandleRecordStream: composing recording")
|
||||
log.Log.Info("HandleRecordStream: write header")
|
||||
|
||||
// Creating the file, might block sometimes.
|
||||
if err := myMuxer.WriteHeader(streams); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
|
||||
if err := myMuxer.WritePacket(pkt); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
|
||||
recordingStatus = "started"
|
||||
|
||||
} else if start {
|
||||
if err := myMuxer.WritePacket(pkt); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
|
||||
// We will sync to file every keyframe.
|
||||
if pkt.IsKeyFrame {
|
||||
err := file.Sync()
|
||||
if err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
} else {
|
||||
log.Log.Info("HandleRecordStream: Synced file: " + name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start = true
|
||||
timestamp = now
|
||||
pkt = nextPkt
|
||||
}
|
||||
|
||||
// We might have interrupted the recording while restarting the agent.
|
||||
// If this happens we need to check to properly close the recording.
|
||||
if cursorError != nil {
|
||||
if recordingStatus == "started" {
|
||||
|
||||
// This will write the trailer a well.
|
||||
if err := myMuxer.WriteTrailer(); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
|
||||
log.Log.Info("HandleRecordStream: Recording finished: file save: " + name)
|
||||
// Cleanup muxer
|
||||
start = false
|
||||
myMuxer.Close()
|
||||
myMuxer = nil
|
||||
file.Close()
|
||||
file = nil
|
||||
|
||||
// Check if need to convert to fragmented using bento
|
||||
if config.Capture.Fragmented == "true" && config.Capture.FragmentedDuration > 0 {
|
||||
utils.CreateFragmentedMP4(fullName, config.Capture.FragmentedDuration)
|
||||
}
|
||||
|
||||
// Create a symbol link.
|
||||
fc, _ := os.Create("./data/cloud/" + name)
|
||||
fc.Close()
|
||||
|
||||
recordingStatus = "idle"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
log.Log.Info("HandleRecordStream: Start motion based recording ")
|
||||
|
||||
var myMuxer *mp4.Muxer
|
||||
var file *os.File
|
||||
var err error
|
||||
|
||||
for motion := range communication.HandleMotion {
|
||||
|
||||
timestamp = time.Now().Unix()
|
||||
startRecording = time.Now().Unix() // we mark the current time when the record started.
|
||||
numberOfChanges := motion.NumberOfChanges
|
||||
|
||||
// timestamp_microseconds_instanceName_regionCoordinates_numberOfChanges_token
|
||||
// 1564859471_6-474162_oprit_577-283-727-375_1153_27.mp4
|
||||
@@ -167,185 +287,172 @@ func HandleRecordStream(recordingCursor *pubsub.QueueCursor, configuration *mode
|
||||
// - Number of changes
|
||||
// - Token
|
||||
|
||||
startRecording = time.Now().Unix() // we mark the current time when the record started.ss
|
||||
s := strconv.FormatInt(startRecording, 10) + "_" +
|
||||
"6" + "-" +
|
||||
"967003" + "_" +
|
||||
config.Name + "_" +
|
||||
"200-200-400-400" + "_0_" +
|
||||
"200-200-400-400" + "_" +
|
||||
strconv.Itoa(numberOfChanges) + "_" +
|
||||
"769"
|
||||
|
||||
name = s + ".mp4"
|
||||
fullName = "./data/recordings/" + name
|
||||
name := s + ".mp4"
|
||||
fullName := "./data/recordings/" + name
|
||||
|
||||
// Running...
|
||||
log.Log.Info("Recording started")
|
||||
|
||||
log.Log.Info("HandleRecordStream: Recording started")
|
||||
file, err = os.Create(fullName)
|
||||
if err == nil {
|
||||
myMuxer = mp4.NewMuxer(file)
|
||||
}
|
||||
|
||||
start := false
|
||||
|
||||
log.Log.Info("HandleRecordStream: composing recording")
|
||||
log.Log.Info("HandleRecordStream: write header")
|
||||
|
||||
// Creating the file, might block sometimes.
|
||||
if err := myMuxer.WriteHeader(streams); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
|
||||
if err := myMuxer.WritePacket(pkt); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
// Get as much packets we need.
|
||||
var cursorError error
|
||||
var pkt av.Packet
|
||||
var nextPkt av.Packet
|
||||
recordingCursor := queue.Oldest()
|
||||
|
||||
if cursorError == nil {
|
||||
pkt, cursorError = recordingCursor.ReadPacket()
|
||||
}
|
||||
|
||||
recordingStatus = "started"
|
||||
for cursorError == nil {
|
||||
|
||||
} else if start {
|
||||
if err := myMuxer.WritePacket(pkt); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
nextPkt, cursorError = recordingCursor.ReadPacket()
|
||||
if cursorError != nil {
|
||||
log.Log.Error("HandleRecordStream: " + cursorError.Error())
|
||||
}
|
||||
|
||||
// We might have interrupted the recording while restarting the agent.
|
||||
// If this happens we need to check to properly close the recording.
|
||||
if cursorError != nil {
|
||||
if recordingStatus == "started" {
|
||||
// This will write the trailer a well.
|
||||
if err := myMuxer.WriteTrailer(); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
now := time.Now().Unix()
|
||||
select {
|
||||
case motion := <-communication.HandleMotion:
|
||||
timestamp = now
|
||||
log.Log.Info("HandleRecordStream: motion detected while recording. Expanding recording.")
|
||||
numberOfChanges = motion.NumberOfChanges
|
||||
log.Log.Info("Received message with recording data, detected changes to save: " + strconv.Itoa(numberOfChanges))
|
||||
default:
|
||||
}
|
||||
|
||||
if (timestamp+recordingPeriod-now < 0 || now-startRecording > maxRecordingPeriod) && nextPkt.IsKeyFrame {
|
||||
log.Log.Info("HandleRecordStream: closing recording (timestamp: " + strconv.FormatInt(timestamp, 10) + ", recordingPeriod: " + strconv.FormatInt(recordingPeriod, 10) + ", now: " + strconv.FormatInt(now, 10) + ", startRecording: " + strconv.FormatInt(startRecording, 10) + ", maxRecordingPeriod: " + strconv.FormatInt(maxRecordingPeriod, 10))
|
||||
break
|
||||
}
|
||||
if pkt.IsKeyFrame && !start {
|
||||
log.Log.Info("HandleRecordStream: write frames")
|
||||
start = true
|
||||
}
|
||||
if start {
|
||||
if err := myMuxer.WritePacket(pkt); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
|
||||
// We will sync to file every keyframe.
|
||||
if pkt.IsKeyFrame {
|
||||
err := file.Sync()
|
||||
if err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
} else {
|
||||
log.Log.Info("HandleRecordStream: Synced file: " + name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pkt = nextPkt
|
||||
}
|
||||
|
||||
log.Log.Info("HandleRecordStream: Recording finished: file save: " + name)
|
||||
// This will write the trailer as well.
|
||||
myMuxer.WriteTrailerWithPacket(nextPkt)
|
||||
log.Log.Info("HandleRecordStream: file save: " + name)
|
||||
|
||||
// Cleanup muxer
|
||||
myMuxer.Close()
|
||||
myMuxer = nil
|
||||
file.Close()
|
||||
file = nil
|
||||
|
||||
// Check if need to convert to fragmented using bento
|
||||
if config.Capture.Fragmented == "true" && config.Capture.FragmentedDuration > 0 {
|
||||
utils.CreateFragmentedMP4(fullName, config.Capture.FragmentedDuration)
|
||||
}
|
||||
|
||||
// Create a symbol link.
|
||||
// Create a symbol linc.
|
||||
fc, _ := os.Create("./data/cloud/" + name)
|
||||
fc.Close()
|
||||
|
||||
// Cleanup muxer
|
||||
start = false
|
||||
myMuxer = nil
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
|
||||
recordingStatus = "idle"
|
||||
// Clean up the recording directory if necessary.
|
||||
CleanupRecordingDirectory(configuration)
|
||||
}
|
||||
}
|
||||
|
||||
log.Log.Debug("HandleRecordStream: finished")
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyCamera godoc
|
||||
// @Router /api/camera/verify/{streamType} [post]
|
||||
// @ID verify-camera
|
||||
// @Tags camera
|
||||
// @Param streamType path string true "Stream Type" Enums(primary, secondary)
|
||||
// @Param cameraStreams body models.CameraStreams true "Camera Streams"
|
||||
// @Summary Validate a specific RTSP profile camera connection.
|
||||
// @Description This method will validate a specific profile connection from an RTSP camera, and try to get the codec.
|
||||
// @Success 200 {object} models.APIResponse
|
||||
func VerifyCamera(c *gin.Context) {
|
||||
|
||||
var cameraStreams models.CameraStreams
|
||||
err := c.BindJSON(&cameraStreams)
|
||||
|
||||
if err == nil {
|
||||
|
||||
streamType := c.Param("streamType")
|
||||
if streamType == "" {
|
||||
streamType = "primary"
|
||||
}
|
||||
|
||||
rtspUrl := cameraStreams.RTSP
|
||||
if streamType == "secondary" {
|
||||
rtspUrl = cameraStreams.SubRTSP
|
||||
}
|
||||
_, codecs, err := OpenRTSP(rtspUrl)
|
||||
if err == nil {
|
||||
|
||||
videoIdx := -1
|
||||
audioIdx := -1
|
||||
for i, codec := range codecs {
|
||||
if codec.Type().String() == "H264" && videoIdx < 0 {
|
||||
videoIdx = i
|
||||
} else if codec.Type().String() == "PCM_MULAW" && audioIdx < 0 {
|
||||
audioIdx = i
|
||||
}
|
||||
}
|
||||
|
||||
if videoIdx > -1 {
|
||||
c.JSON(200, models.APIResponse{
|
||||
Message: "All good, detected a H264 codec.",
|
||||
Data: codecs,
|
||||
})
|
||||
} else {
|
||||
c.JSON(400, models.APIResponse{
|
||||
Message: "Stream doesn't have a H264 codec, we only support H264 so far.",
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
c.JSON(400, models.APIResponse{
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
|
||||
log.Log.Info("HandleRecordStream: Start motion based recording ")
|
||||
|
||||
var myMuxer *mp4.Muxer
|
||||
var file *os.File
|
||||
var err error
|
||||
|
||||
for motion := range communication.HandleMotion {
|
||||
|
||||
timestamp = time.Now().Unix()
|
||||
startRecording = time.Now().Unix() // we mark the current time when the record started.
|
||||
numberOfChanges := motion.NumberOfChanges
|
||||
|
||||
// timestamp_microseconds_instanceName_regionCoordinates_numberOfChanges_token
|
||||
// 1564859471_6-474162_oprit_577-283-727-375_1153_27.mp4
|
||||
// - Timestamp
|
||||
// - Size + - + microseconds
|
||||
// - device
|
||||
// - Region
|
||||
// - Number of changes
|
||||
// - Token
|
||||
|
||||
s := strconv.FormatInt(startRecording, 10) + "_" +
|
||||
"6" + "-" +
|
||||
"967003" + "_" +
|
||||
config.Name + "_" +
|
||||
"200-200-400-400" + "_" +
|
||||
strconv.Itoa(numberOfChanges) + "_" +
|
||||
"769"
|
||||
|
||||
name := s + ".mp4"
|
||||
fullName := "./data/recordings/" + name
|
||||
|
||||
// Running...
|
||||
log.Log.Info("HandleRecordStream: Recording started")
|
||||
file, err = os.Create(fullName)
|
||||
if err == nil {
|
||||
myMuxer = mp4.NewMuxer(file)
|
||||
}
|
||||
|
||||
start := false
|
||||
|
||||
log.Log.Info("HandleRecordStream: composing recording")
|
||||
log.Log.Info("HandleRecordStream: write header")
|
||||
// Creating the file, might block sometimes.
|
||||
if err := myMuxer.WriteHeader(streams); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
|
||||
// Get as much packets we need.
|
||||
//for pkt := range packets {
|
||||
|
||||
var cursorError error
|
||||
var pkt av.Packet
|
||||
|
||||
for cursorError == nil {
|
||||
|
||||
pkt, cursorError = recordingCursor.ReadPacket()
|
||||
if cursorError != nil {
|
||||
log.Log.Error("HandleRecordStream: " + cursorError.Error())
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
select {
|
||||
case motion := <-communication.HandleMotion:
|
||||
timestamp = now
|
||||
log.Log.Info("HandleRecordStream: motion detected while recording. Expanding recording.")
|
||||
numberOfChanges = motion.NumberOfChanges
|
||||
log.Log.Info("Received message with recording data, detected changes to save: " + strconv.Itoa(numberOfChanges))
|
||||
default:
|
||||
}
|
||||
|
||||
if timestamp+recordingPeriod-now < 0 || now-startRecording > maxRecordingPeriod {
|
||||
log.Log.Info("HandleRecordStream: closing recording (timestamp: " + strconv.FormatInt(timestamp, 10) + ", recordingPeriod: " + strconv.FormatInt(recordingPeriod, 10) + ", now: " + strconv.FormatInt(now, 10) + ", startRecording: " + strconv.FormatInt(startRecording, 10) + ", maxRecordingPeriod: " + strconv.FormatInt(maxRecordingPeriod, 10))
|
||||
break
|
||||
}
|
||||
if pkt.IsKeyFrame && start == false {
|
||||
log.Log.Info("HandleRecordStream: write frames")
|
||||
start = true
|
||||
}
|
||||
if start {
|
||||
if err := myMuxer.WritePacket(pkt); err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This will write the trailer as well.
|
||||
myMuxer.WriteTrailer()
|
||||
log.Log.Info("HandleRecordStream: file save: " + name)
|
||||
file.Close()
|
||||
myMuxer = nil
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
|
||||
// Check if need to convert to fragmented using bento
|
||||
if config.Capture.Fragmented == "true" && config.Capture.FragmentedDuration > 0 {
|
||||
utils.CreateFragmentedMP4(fullName, config.Capture.FragmentedDuration)
|
||||
}
|
||||
|
||||
// Create a symbol linc.
|
||||
fc, _ := os.Create("./data/cloud/" + name)
|
||||
fc.Close()
|
||||
|
||||
// Clean up the recording directory if necessary.
|
||||
CleanupRecordingDirectory(configuration)
|
||||
}
|
||||
c.JSON(400, models.APIResponse{
|
||||
Message: "Something went wrong while receiving the config " + err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
log.Log.Debug("HandleRecordStream: finished")
|
||||
}
|
||||
|
||||
@@ -8,31 +8,30 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/elastic/go-sysinfo"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-module/carbon/v2"
|
||||
"github.com/kerberos-io/joy4/av/pubsub"
|
||||
"github.com/minio/minio-go/v6"
|
||||
|
||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||
av "github.com/kerberos-io/joy4/av"
|
||||
"github.com/kerberos-io/joy4/cgo/ffmpeg"
|
||||
"gocv.io/x/gocv"
|
||||
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/kerberos-io/agent/machinery/src/computervision"
|
||||
"github.com/kerberos-io/agent/machinery/src/log"
|
||||
"github.com/kerberos-io/agent/machinery/src/models"
|
||||
"github.com/kerberos-io/agent/machinery/src/onvif"
|
||||
"github.com/kerberos-io/agent/machinery/src/utils"
|
||||
"github.com/kerberos-io/agent/machinery/src/webrtc"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"github.com/shirou/gopsutil/host"
|
||||
)
|
||||
|
||||
func PendingUpload() {
|
||||
@@ -92,8 +91,66 @@ func HandleUpload(configuration *models.Configuration, communication *models.Com
|
||||
log.Log.Debug("HandleUpload: finished")
|
||||
}
|
||||
|
||||
func HandleHeartBeat(configuration *models.Configuration, communication *models.Communication) {
|
||||
func GetSystemInfo() (models.System, error) {
|
||||
var usedMem uint64 = 0
|
||||
var totalMem uint64 = 0
|
||||
var freeMem uint64 = 0
|
||||
architecture := ""
|
||||
cpuId := ""
|
||||
KernelVersion := ""
|
||||
agentVersion := ""
|
||||
var MACs []string
|
||||
var IPs []string
|
||||
hostname := ""
|
||||
bootTime := time.Time{}
|
||||
|
||||
// Read agent version
|
||||
version, err := os.Open("./version")
|
||||
defer version.Close()
|
||||
agentVersion = "unknown"
|
||||
if err == nil {
|
||||
agentVersionBytes, err := ioutil.ReadAll(version)
|
||||
agentVersion = string(agentVersionBytes)
|
||||
if err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
host, err := sysinfo.Host()
|
||||
if err == nil {
|
||||
cpuId = host.Info().UniqueID
|
||||
architecture = host.Info().Architecture
|
||||
KernelVersion = host.Info().KernelVersion
|
||||
MACs = host.Info().MACs
|
||||
IPs = host.Info().IPs
|
||||
hostname = host.Info().Hostname
|
||||
bootTime = host.Info().BootTime
|
||||
memory, err := host.Memory()
|
||||
if err == nil {
|
||||
usedMem = memory.Used
|
||||
totalMem = memory.Total
|
||||
freeMem = memory.Free
|
||||
}
|
||||
}
|
||||
|
||||
system := models.System{
|
||||
Hostname: hostname,
|
||||
CPUId: cpuId,
|
||||
KernelVersion: KernelVersion,
|
||||
Version: agentVersion,
|
||||
MACs: MACs,
|
||||
IPs: IPs,
|
||||
BootTime: uint64(bootTime.Unix()),
|
||||
Architecture: architecture,
|
||||
UsedMemory: usedMem,
|
||||
TotalMemory: totalMem,
|
||||
FreeMemory: freeMem,
|
||||
}
|
||||
|
||||
return system, nil
|
||||
}
|
||||
|
||||
func HandleHeartBeat(configuration *models.Configuration, communication *models.Communication, uptimeStart time.Time) {
|
||||
log.Log.Debug("HandleHeartBeat: started")
|
||||
|
||||
config := configuration.Config
|
||||
@@ -107,6 +164,7 @@ func HandleHeartBeat(configuration *models.Configuration, communication *models.
|
||||
username := ""
|
||||
vaultURI := ""
|
||||
|
||||
username = config.S3.Username
|
||||
if config.Cloud == "s3" && config.S3 != nil && config.S3.Publickey != "" {
|
||||
username = config.S3.Username
|
||||
key = config.S3.Publickey
|
||||
@@ -127,17 +185,34 @@ func HandleHeartBeat(configuration *models.Configuration, communication *models.
|
||||
loop:
|
||||
for {
|
||||
|
||||
uptime, _ := host.Uptime()
|
||||
days := strconv.Itoa(int(uptime / (60 * 60 * 24)))
|
||||
//12:11:48 up 11 days
|
||||
// Check if we have a friendly name or not.
|
||||
name := config.Name
|
||||
if config.FriendlyName != "" {
|
||||
name = config.FriendlyName
|
||||
}
|
||||
|
||||
//partitions, _ := disk.Partitions(false)
|
||||
usage, _ := disk.Usage("/")
|
||||
diskPercentUsed := strconv.Itoa(int(usage.UsedPercent))
|
||||
// Get some system information
|
||||
// like the uptime, hostname, memory usage, etc.
|
||||
system, _ := GetSystemInfo()
|
||||
|
||||
// We will formated the uptime to a human readable format
|
||||
// this will be used on Kerberos Hub: Uptime -> 1 day and 2 hours.
|
||||
uptimeFormatted := uptimeStart.Format("2006-01-02 15:04:05")
|
||||
uptimeString := carbon.Parse(uptimeFormatted).DiffForHumans()
|
||||
uptimeString = strings.ReplaceAll(uptimeString, "ago", "")
|
||||
|
||||
// We'll check which mode is enabled for the camera.
|
||||
onvifEnabled := "false"
|
||||
if config.Capture.IPCamera.ONVIFXAddr != "" {
|
||||
onvifEnabled = "true"
|
||||
device, err := onvif.ConnectToOnvifDevice(configuration)
|
||||
if err == nil {
|
||||
capabilities := onvif.GetCapabilitiesFromDevice(device)
|
||||
for _, v := range capabilities {
|
||||
if v == "PTZ" || v == "ptz" {
|
||||
onvifEnabled = "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the agent is running inside a cluster (Kerberos Factory) or as
|
||||
@@ -148,31 +223,35 @@ func HandleHeartBeat(configuration *models.Configuration, communication *models.
|
||||
}
|
||||
|
||||
var object = fmt.Sprintf(`{
|
||||
"key" : "%s",
|
||||
"hash" : "826133658",
|
||||
"version" : "3.0.0",
|
||||
"cpuid" : "Serial: xxx",
|
||||
"clouduser" : "%s",
|
||||
"cloudpublickey" : "%s",
|
||||
"cameraname" : "%s",
|
||||
"cameratype" : "IPCamera",
|
||||
"docker" : true,
|
||||
"kios" : false,
|
||||
"raspberrypi" : false,
|
||||
"enterprise" : %t,
|
||||
"board" : "",
|
||||
"disk1size" : "%s",
|
||||
"disk3size" : "%s",
|
||||
"diskvdasize" : "%s",
|
||||
"numberoffiles" : "33",
|
||||
"temperature" : "sh: 1: vcgencmd: not found",
|
||||
"wifissid" : "",
|
||||
"wifistrength" : "",
|
||||
"uptime" : "up %s days,",
|
||||
"timestamp" : 1564747908,
|
||||
"siteID" : "%s",
|
||||
"onvif" : "%s"
|
||||
}`, config.Key, username, key, config.Name, isEnterprise, "0", "0", diskPercentUsed, days, config.HubSite, onvifEnabled)
|
||||
"key" : "%s",
|
||||
"version" : "3.0.0",
|
||||
"release" : "%s",
|
||||
"cpuid" : "%s",
|
||||
"clouduser" : "%s",
|
||||
"cloudpublickey" : "%s",
|
||||
"cameraname" : "%s",
|
||||
"enterprise" : %t,
|
||||
"hostname" : "%s",
|
||||
"architecture" : "%s",
|
||||
"freeMemory" : "%d",
|
||||
"usedMemory" : "%d",
|
||||
"totalMemory" : "%d",
|
||||
"macs" : "%v",
|
||||
"ips" : "%v",
|
||||
"board" : "",
|
||||
"disk1size" : "%s",
|
||||
"disk3size" : "%s",
|
||||
"diskvdasize" : "%s",
|
||||
"uptime" : "%s",
|
||||
"siteID" : "%s",
|
||||
"onvif" : "%s",
|
||||
"numberoffiles" : "33",
|
||||
"timestamp" : 1564747908,
|
||||
"cameratype" : "IPCamera",
|
||||
"docker" : true,
|
||||
"kios" : false,
|
||||
"raspberrypi" : false
|
||||
}`, config.Key, system.Version, system.CPUId, username, key, name, isEnterprise, system.Hostname, system.Architecture, system.TotalMemory, system.UsedMemory, system.FreeMemory, system.MACs, system.IPs, "0", "0", "0", uptimeString, config.HubSite, onvifEnabled)
|
||||
|
||||
var jsonStr = []byte(object)
|
||||
buffy := bytes.NewBuffer(jsonStr)
|
||||
@@ -274,15 +353,14 @@ func HandleLiveStreamSD(livestreamCursor *pubsub.QueueCursor, configuration *mod
|
||||
}
|
||||
|
||||
func sendImage(topic string, mqttClient mqtt.Client, pkt av.Packet, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) {
|
||||
mat := computervision.GetRGBImage(pkt, decoder, decoderMutex)
|
||||
buffer, err := gocv.IMEncode(gocv.JPEGFileExt, mat)
|
||||
mat.Close()
|
||||
img, err := computervision.GetRawImage(pkt, decoder, decoderMutex)
|
||||
if err == nil {
|
||||
encoded := base64.StdEncoding.EncodeToString(buffer.GetBytes())
|
||||
bytes, _ := computervision.ImageToBytes(&img.Image)
|
||||
encoded := base64.StdEncoding.EncodeToString(bytes)
|
||||
mqttClient.Publish(topic, 0, false, encoded)
|
||||
}
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
// Cleanup the image.
|
||||
img.Free()
|
||||
}
|
||||
|
||||
func HandleLiveStreamHD(livestreamCursor *pubsub.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, codecs []av.CodecData, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) {
|
||||
@@ -344,8 +422,6 @@ func VerifyHub(c *gin.Context) {
|
||||
|
||||
if err == nil {
|
||||
hubKey := config.HubKey
|
||||
//hubPrivateKey := config.HubPrivateKey
|
||||
//hubSite := config.HubSite
|
||||
hubURI := config.HubURI
|
||||
|
||||
content := []byte(`{"message": "fake-message"}`)
|
||||
@@ -409,7 +485,6 @@ func VerifyPersistence(c *gin.Context) {
|
||||
|
||||
if config.Cloud == "s3" {
|
||||
|
||||
//fmt.Println("Uploading...")
|
||||
// timestamp_microseconds_instanceName_regionCoordinates_numberOfChanges_token
|
||||
// 1564859471_6-474162_oprit_577-283-727-375_1153_27.mp4
|
||||
// - Timestamp
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/InVisionApp/conjungo"
|
||||
@@ -180,6 +182,171 @@ func OpenConfig(configuration *models.Configuration) {
|
||||
return
|
||||
}
|
||||
|
||||
// This function will override the configuration with environment variables.
|
||||
func OverrideWithEnvironmentVariables(configuration *models.Configuration) {
|
||||
environmentVariables := os.Environ()
|
||||
for _, env := range environmentVariables {
|
||||
if strings.Contains(env, "AGENT_") {
|
||||
key := strings.Split(env, "=")[0]
|
||||
value := os.Getenv(key)
|
||||
switch key {
|
||||
|
||||
/* General configuration */
|
||||
case "AGENT_KEY":
|
||||
configuration.Config.Key = value
|
||||
break
|
||||
case "AGENT_NAME":
|
||||
configuration.Config.Name = value
|
||||
break
|
||||
case "AGENT_TIMEZONE":
|
||||
configuration.Config.Timezone = value
|
||||
break
|
||||
case "AGENT_OFFLINE":
|
||||
configuration.Config.Offline = value
|
||||
break
|
||||
case "AGENT_AUTO_CLEAN":
|
||||
configuration.Config.AutoClean = value
|
||||
break
|
||||
case "AGENT_AUTO_CLEAN_MAX_SIZE":
|
||||
size, err := strconv.ParseInt(value, 10, 64)
|
||||
if err == nil {
|
||||
configuration.Config.MaxDirectorySize = size
|
||||
}
|
||||
break
|
||||
|
||||
/* Camera configuration */
|
||||
case "AGENT_CAPTURE_IPCAMERA_RTSP":
|
||||
configuration.Config.Capture.IPCamera.RTSP = value
|
||||
break
|
||||
case "AGENT_CAPTURE_IPCAMERA_SUB_RTSP":
|
||||
configuration.Config.Capture.IPCamera.SubRTSP = value
|
||||
break
|
||||
|
||||
/* ONVIF connnection settings */
|
||||
case "AGENT_CAPTURE_IPCAMERA_ONVIF":
|
||||
isEnabled := value == " true"
|
||||
configuration.Config.Capture.IPCamera.ONVIF = isEnabled
|
||||
break
|
||||
case "AGENT_CAPTURE_IPCAMERA_ONVIF_XADDR":
|
||||
configuration.Config.Capture.IPCamera.ONVIFXAddr = value
|
||||
break
|
||||
case "AGENT_CAPTURE_IPCAMERA_ONVIF_USERNAME":
|
||||
configuration.Config.Capture.IPCamera.ONVIFUsername = value
|
||||
break
|
||||
case "AGENT_CAPTURE_IPCAMERA_ONVIF_PASSWORD":
|
||||
configuration.Config.Capture.IPCamera.ONVIFPassword = value
|
||||
break
|
||||
|
||||
/* Recording mode */
|
||||
case "AGENT_CAPTURE_RECORDING":
|
||||
configuration.Config.Capture.Recording = value
|
||||
break
|
||||
case "AGENT_CAPTURE_CONTINUOUS":
|
||||
configuration.Config.Capture.Continuous = value
|
||||
break
|
||||
case "AGENT_CAPTURE_PRERECORDING":
|
||||
duration, err := strconv.ParseInt(value, 10, 64)
|
||||
if err == nil {
|
||||
configuration.Config.Capture.PreRecording = duration
|
||||
}
|
||||
break
|
||||
case "AGENT_CAPTURE_POSTRECORDING":
|
||||
duration, err := strconv.ParseInt(value, 10, 64)
|
||||
if err == nil {
|
||||
configuration.Config.Capture.PostRecording = duration
|
||||
}
|
||||
break
|
||||
case "AGENT_CAPTURE_MAXLENGTH":
|
||||
duration, err := strconv.ParseInt(value, 10, 64)
|
||||
if err == nil {
|
||||
configuration.Config.Capture.MaxLengthRecording = duration
|
||||
}
|
||||
break
|
||||
case "AGENT_CAPTURE_PIXEL_CHANGE":
|
||||
count, err := strconv.Atoi(value)
|
||||
if err == nil {
|
||||
configuration.Config.Capture.PixelChangeThreshold = count
|
||||
}
|
||||
break
|
||||
case "AGENT_CAPTURE_FRAGMENTED":
|
||||
configuration.Config.Capture.Fragmented = value
|
||||
break
|
||||
case "AGENT_CAPTURE_FRAGMENTED_DURATION":
|
||||
duration, err := strconv.ParseInt(value, 10, 64)
|
||||
if err == nil {
|
||||
configuration.Config.Capture.FragmentedDuration = duration
|
||||
}
|
||||
break
|
||||
|
||||
/* Cloud settings for persisting recordings */
|
||||
case "AGENT_CLOUD":
|
||||
configuration.Config.Cloud = value
|
||||
break
|
||||
|
||||
/* When connected and storing in Kerberos Hub (SAAS) */
|
||||
case "AGENT_HUB_URI":
|
||||
configuration.Config.HubURI = value
|
||||
break
|
||||
case "AGENT_HUB_KEY":
|
||||
configuration.Config.HubKey = value
|
||||
break
|
||||
case "AGENT_HUB_PRIVATE_KEY":
|
||||
configuration.Config.HubPrivateKey = value
|
||||
break
|
||||
case "AGENT_HUB_USERNAME":
|
||||
configuration.Config.S3.Username = value
|
||||
break
|
||||
case "AGENT_HUB_SITE":
|
||||
configuration.Config.HubSite = value
|
||||
break
|
||||
|
||||
/* MQTT settings for bi-directional communication */
|
||||
case "AGENT_MQTT_URI":
|
||||
configuration.Config.MQTTURI = value
|
||||
break
|
||||
case "AGENT_MQTT_USERNAME":
|
||||
configuration.Config.MQTTUsername = value
|
||||
break
|
||||
case "AGENT_MQTT_PASSWORD":
|
||||
configuration.Config.MQTTPassword = value
|
||||
break
|
||||
|
||||
/* WebRTC settings for live-streaming (remote) */
|
||||
case "AGENT_STUN_URI":
|
||||
configuration.Config.STUNURI = value
|
||||
break
|
||||
case "AGENT_TURN_URI":
|
||||
configuration.Config.TURNURI = value
|
||||
break
|
||||
case "AGENT_TURN_USERNAME":
|
||||
configuration.Config.TURNUsername = value
|
||||
break
|
||||
case "AGENT_TURN_PASSWORD":
|
||||
configuration.Config.TURNPassword = value
|
||||
break
|
||||
|
||||
/* When storing in a Kerberos Vault */
|
||||
case "AGENT_KERBEROSVAULT_URI":
|
||||
configuration.Config.KStorage.URI = value
|
||||
break
|
||||
case "AGENT_KERBEROSVAULT_ACCESS_KEY":
|
||||
configuration.Config.KStorage.AccessKey = value
|
||||
break
|
||||
case "AGENT_KERBEROSVAULT_SECRET_KEY":
|
||||
configuration.Config.KStorage.SecretAccessKey = value
|
||||
break
|
||||
case "AGENT_KERBEROSVAULT_PROVIDER":
|
||||
configuration.Config.KStorage.Provider = value
|
||||
break
|
||||
case "AGENT_KERBEROSVAULT_DIRECTORY":
|
||||
configuration.Config.KStorage.Directory = value
|
||||
break
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SaveConfig(config models.Config, configuration *models.Configuration, communication *models.Communication) error {
|
||||
if !communication.IsConfiguring.IsSet() {
|
||||
communication.IsConfiguring.Set()
|
||||
|
||||
@@ -13,13 +13,19 @@ import (
|
||||
"github.com/kerberos-io/agent/machinery/src/models"
|
||||
"github.com/kerberos-io/agent/machinery/src/onvif"
|
||||
routers "github.com/kerberos-io/agent/machinery/src/routers/mqtt"
|
||||
"github.com/kerberos-io/joy4/av"
|
||||
"github.com/kerberos-io/joy4/av/pubsub"
|
||||
"github.com/kerberos-io/joy4/cgo/ffmpeg"
|
||||
"github.com/tevino/abool"
|
||||
)
|
||||
|
||||
func Bootstrap(configuration *models.Configuration, communication *models.Communication) {
|
||||
log.Log.Debug("Bootstrap: started")
|
||||
|
||||
// We will keep track of the Kerberos Agent up time
|
||||
// This is send to Kerberos Hub in a heartbeat.
|
||||
uptimeStart := time.Now()
|
||||
|
||||
// Initiate the packet counter, this is being used to detect
|
||||
// if a camera is going blocky, or got disconnected.
|
||||
var packageCounter atomic.Value
|
||||
@@ -39,6 +45,7 @@ func Bootstrap(configuration *models.Configuration, communication *models.Commun
|
||||
communication.CloudTimestamp = &cloudTimestamp
|
||||
|
||||
communication.HandleStream = make(chan string, 1)
|
||||
communication.HandleSubStream = make(chan string, 1)
|
||||
communication.HandleUpload = make(chan string, 1)
|
||||
communication.HandleHeartBeat = make(chan string, 1)
|
||||
communication.HandleLiveSD = make(chan int64, 1)
|
||||
@@ -55,7 +62,7 @@ func Bootstrap(configuration *models.Configuration, communication *models.Commun
|
||||
|
||||
for {
|
||||
// This will blocking until receiving a signal to be restarted, reconfigured, stopped, etc.
|
||||
status := RunAgent(configuration, communication)
|
||||
status := RunAgent(configuration, communication, uptimeStart)
|
||||
if status == "stop" {
|
||||
break
|
||||
}
|
||||
@@ -65,7 +72,7 @@ func Bootstrap(configuration *models.Configuration, communication *models.Commun
|
||||
log.Log.Debug("Bootstrap: finished")
|
||||
}
|
||||
|
||||
func RunAgent(configuration *models.Configuration, communication *models.Communication) string {
|
||||
func RunAgent(configuration *models.Configuration, communication *models.Communication, uptimeStart time.Time) string {
|
||||
log.Log.Debug("RunAgent: started")
|
||||
|
||||
config := configuration.Config
|
||||
@@ -76,55 +83,113 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
|
||||
rtspUrl := config.Capture.IPCamera.RTSP
|
||||
infile, streams, err := capture.OpenRTSP(rtspUrl)
|
||||
|
||||
//var decoder *ffmpeg.VideoDecoder
|
||||
var queue *pubsub.Queue
|
||||
|
||||
status := "not started"
|
||||
|
||||
if err == nil {
|
||||
|
||||
log.Log.Info("RunAgent: opened RTSP stream")
|
||||
|
||||
// We might have a secondary rtsp url, so we might need to use that.
|
||||
var subInfile av.DemuxCloser
|
||||
var subStreams []av.CodecData
|
||||
subStreamEnabled := false
|
||||
subRtspUrl := config.Capture.IPCamera.SubRTSP
|
||||
if subRtspUrl != "" && subRtspUrl != rtspUrl {
|
||||
subInfile, subStreams, err = capture.OpenRTSP(subRtspUrl)
|
||||
if err == nil {
|
||||
log.Log.Info("RunAgent: opened RTSP sub stream")
|
||||
subStreamEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
// At some routines we will need to decode the image.
|
||||
// Make sure its properly locked as we only have a single decoder.
|
||||
var decoderMutex sync.Mutex
|
||||
var subDecoderMutex sync.Mutex
|
||||
decoder := capture.GetVideoDecoder(streams)
|
||||
|
||||
var subDecoder *ffmpeg.VideoDecoder
|
||||
if subStreamEnabled {
|
||||
subDecoder = capture.GetVideoDecoder(subStreams)
|
||||
}
|
||||
|
||||
communication.Decoder = decoder
|
||||
communication.SubDecoder = subDecoder
|
||||
communication.DecoderMutex = &decoderMutex
|
||||
communication.SubDecoderMutex = &subDecoderMutex
|
||||
|
||||
// Create a packet queue, which is filled by the HandleStream routing
|
||||
// and consumed by all other routines: motion, livestream, etc.
|
||||
if config.Capture.PreRecording <= 0 {
|
||||
config.Capture.PreRecording = 1
|
||||
log.Log.Warning("RunAgent: Prerecording value not found in config or invalid value! Found: " + strconv.FormatInt(config.Capture.PreRecording, 10))
|
||||
}
|
||||
|
||||
// We are creating a queue to store the RTSP frames in, these frames will be
|
||||
// processed by the different consumers: motion detection, recording, etc.
|
||||
queue = pubsub.NewQueue()
|
||||
communication.Queue = queue
|
||||
queue.SetMaxGopCount(int(config.Capture.PreRecording)) // GOP time frame is set to prerecording.
|
||||
log.Log.Info("RunAgent: SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)))
|
||||
queue.WriteHeader(streams)
|
||||
|
||||
// We might have a substream, if so we'll create a seperate queue.
|
||||
var subQueue *pubsub.Queue
|
||||
if subStreamEnabled {
|
||||
log.Log.Info("RunAgent: Creating sub stream queue with SetMaxGopCount set to " + strconv.Itoa(int(config.Capture.PreRecording)))
|
||||
subQueue = pubsub.NewQueue()
|
||||
subQueue.SetMaxGopCount(1)
|
||||
subQueue.WriteHeader(subStreams)
|
||||
}
|
||||
|
||||
// Configure a MQTT client which helps for a bi-directional communication
|
||||
communication.HandleONVIF = make(chan models.OnvifAction, 1)
|
||||
mqttClient := routers.ConfigureMQTT(configuration, communication)
|
||||
|
||||
// Handle heartbeats
|
||||
go cloud.HandleHeartBeat(configuration, communication)
|
||||
go cloud.HandleHeartBeat(configuration, communication, uptimeStart)
|
||||
|
||||
// Handle the camera stream
|
||||
go capture.HandleStream(infile, queue, communication) //, &wg)
|
||||
go capture.HandleStream(infile, queue, communication)
|
||||
|
||||
// Handle the substream if enabled
|
||||
if subStreamEnabled {
|
||||
go capture.HandleSubStream(subInfile, subQueue, communication)
|
||||
}
|
||||
|
||||
// Handle processing of motion
|
||||
motionCursor := queue.Oldest()
|
||||
communication.HandleMotion = make(chan models.MotionDataPartial, 1)
|
||||
go computervision.ProcessMotion(motionCursor, configuration, communication, mqttClient, decoder, &decoderMutex)
|
||||
if subStreamEnabled {
|
||||
motionCursor := subQueue.Latest()
|
||||
go computervision.ProcessMotion(motionCursor, configuration, communication, mqttClient, subDecoder, &subDecoderMutex)
|
||||
} else {
|
||||
motionCursor := queue.Latest()
|
||||
go computervision.ProcessMotion(motionCursor, configuration, communication, mqttClient, decoder, &decoderMutex)
|
||||
}
|
||||
|
||||
// Handle livestream SD (low resolution over MQTT)
|
||||
livestreamCursor := queue.Oldest()
|
||||
go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, decoder, &decoderMutex)
|
||||
if subStreamEnabled {
|
||||
livestreamCursor := subQueue.Latest()
|
||||
go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, subDecoder, &subDecoderMutex)
|
||||
} else {
|
||||
livestreamCursor := queue.Latest()
|
||||
go cloud.HandleLiveStreamSD(livestreamCursor, configuration, communication, mqttClient, decoder, &decoderMutex)
|
||||
}
|
||||
|
||||
// Handle livestream HD (high resolution over WEBRTC)
|
||||
livestreamHDCursor := queue.Oldest()
|
||||
communication.HandleLiveHDHandshake = make(chan models.SDPPayload, 1)
|
||||
go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, streams, decoder, &decoderMutex)
|
||||
if subStreamEnabled {
|
||||
livestreamHDCursor := subQueue.Latest()
|
||||
go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, subStreams, subDecoder, &decoderMutex)
|
||||
} else {
|
||||
livestreamHDCursor := queue.Latest()
|
||||
go cloud.HandleLiveStreamHD(livestreamHDCursor, configuration, communication, mqttClient, streams, decoder, &decoderMutex)
|
||||
}
|
||||
|
||||
// Handle recording, will write an mp4 to disk.
|
||||
recordingCursor := queue.Oldest()
|
||||
go capture.HandleRecordStream(recordingCursor, configuration, communication, streams)
|
||||
go capture.HandleRecordStream(queue, configuration, communication, streams)
|
||||
|
||||
// Handle Upload to cloud provider (Kerberos Hub, Kerberos Vault and others)
|
||||
go cloud.HandleUpload(configuration, communication)
|
||||
@@ -142,15 +207,29 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
|
||||
communication.HandleHeartBeat <- "stop"
|
||||
communication.HandleUpload <- "stop"
|
||||
}
|
||||
communication.HandleStream <- "stop"
|
||||
if subStreamEnabled {
|
||||
communication.HandleSubStream <- "stop"
|
||||
}
|
||||
time.Sleep(time.Second * 1)
|
||||
|
||||
infile.Close()
|
||||
queue.Close()
|
||||
if subStreamEnabled {
|
||||
subInfile.Close()
|
||||
subQueue.Close()
|
||||
}
|
||||
close(communication.HandleONVIF)
|
||||
close(communication.HandleLiveHDHandshake)
|
||||
close(communication.HandleMotion)
|
||||
routers.DisconnectMQTT(mqttClient)
|
||||
decoder.Close()
|
||||
communication.HandleStream <- "stop"
|
||||
|
||||
// Wait a few seconds to stop the decoder.
|
||||
time.Sleep(time.Second * 3)
|
||||
decoder.Close()
|
||||
if subStreamEnabled {
|
||||
subDecoder.Close()
|
||||
}
|
||||
// Waiting for some seconds to make sure everything is properly closed.
|
||||
log.Log.Info("RunAgent: waiting 3 seconds to make sure everything is properly closed.")
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package computervision
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -17,94 +18,33 @@ import (
|
||||
"github.com/kerberos-io/agent/machinery/src/models"
|
||||
"github.com/kerberos-io/joy4/av/pubsub"
|
||||
|
||||
//"github.com/whorfin/go-libjpeg/jpeg"
|
||||
|
||||
geo "github.com/kellydunn/golang-geo"
|
||||
"github.com/kerberos-io/joy4/av"
|
||||
"github.com/kerberos-io/joy4/cgo/ffmpeg"
|
||||
"gocv.io/x/gocv"
|
||||
)
|
||||
|
||||
func GetRGBImage(pkt av.Packet, dec *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) gocv.Mat {
|
||||
var rgb gocv.Mat
|
||||
img, err := capture.DecodeImage(pkt, dec, decoderMutex)
|
||||
if err == nil && img != nil {
|
||||
rgb, _ = ToRGB8(img.Image)
|
||||
gocv.Resize(rgb, &rgb, image.Pt(rgb.Cols()/4, rgb.Rows()/4), 0, 0, gocv.InterpolationArea)
|
||||
}
|
||||
return rgb
|
||||
}
|
||||
|
||||
func GetImage(pkt av.Packet, dec *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) gocv.Mat {
|
||||
var rgb gocv.Mat
|
||||
img, err := capture.DecodeImage(pkt, dec, decoderMutex)
|
||||
|
||||
if err == nil && img != nil {
|
||||
|
||||
// Check if we need to scale down.
|
||||
width := img.Width()
|
||||
height := img.Height()
|
||||
newWidth := width
|
||||
newHeight := height
|
||||
|
||||
// Try minify twice.
|
||||
scaleFactor := 1.0
|
||||
if newWidth > 800 {
|
||||
newWidth = width / 2
|
||||
newHeight = height / 2
|
||||
scaleFactor *= 2
|
||||
}
|
||||
if newWidth > 800 {
|
||||
newWidth = width / 2
|
||||
newHeight = height / 2
|
||||
scaleFactor *= 2
|
||||
}
|
||||
if newWidth > 800 {
|
||||
newWidth = width / 2
|
||||
newHeight = height / 2
|
||||
scaleFactor *= 2
|
||||
}
|
||||
|
||||
im := img.Image
|
||||
rgb, _ = ToRGB8(im)
|
||||
img.Free()
|
||||
if scaleFactor > 1 {
|
||||
gocv.Resize(rgb, &rgb, image.Pt(newWidth, newHeight), 0, 0, gocv.InterpolationArea)
|
||||
}
|
||||
}
|
||||
return rgb
|
||||
}
|
||||
|
||||
func ToGray(rgb gocv.Mat) (gocv.Mat, error) {
|
||||
gray := gocv.NewMat()
|
||||
gocv.CvtColor(rgb, &gray, gocv.ColorBGRToGray)
|
||||
rgb.Close()
|
||||
return gray, nil
|
||||
}
|
||||
|
||||
func ToRGB8(img image.YCbCr) (gocv.Mat, error) {
|
||||
bounds := img.Bounds()
|
||||
x := bounds.Dx()
|
||||
y := bounds.Dy()
|
||||
bytes := make([]byte, 0, x*y*3)
|
||||
for j := bounds.Min.Y; j < bounds.Max.Y; j++ {
|
||||
for i := bounds.Min.X; i < bounds.Max.X; i++ {
|
||||
iy := img.At(i, j)
|
||||
if iy != nil {
|
||||
r, g, b, _ := iy.RGBA()
|
||||
bytes = append(bytes, byte(b>>8), byte(g>>8), byte(r>>8))
|
||||
}
|
||||
}
|
||||
}
|
||||
return gocv.NewMatFromBytes(y, x, gocv.MatTypeCV8UC3, bytes)
|
||||
}
|
||||
|
||||
func ProcessMotion(motionCursor *pubsub.QueueCursor, configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, decoder *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) { //, wg *sync.WaitGroup) {
|
||||
|
||||
log.Log.Debug("ProcessMotion: started")
|
||||
config := configuration.Config
|
||||
|
||||
var isPixelChangeThresholdReached = false
|
||||
var changesToReturn = 0
|
||||
|
||||
if config.Capture.Continuous == "true" {
|
||||
pixelThreshold := config.Capture.PixelChangeThreshold
|
||||
// Might not be set in the config file, so set it to 150
|
||||
if pixelThreshold == 0 {
|
||||
pixelThreshold = 150
|
||||
}
|
||||
|
||||
if config.Capture.Recording == "false" {
|
||||
|
||||
// We might later add the option to still detect motion, but not record.
|
||||
log.Log.Info("ProcessMotion: Recording disabled, so we do not need motion detection either.")
|
||||
|
||||
} else if config.Capture.Continuous == "true" {
|
||||
|
||||
log.Log.Info("ProcessMotion: Continuous recording, so no motion detection.")
|
||||
|
||||
@@ -115,10 +55,10 @@ func ProcessMotion(motionCursor *pubsub.QueueCursor, configuration *models.Confi
|
||||
key := config.HubKey
|
||||
|
||||
// Initialise first 2 elements
|
||||
var matArray [3]*gocv.Mat
|
||||
var imageArray [3]*image.Gray
|
||||
|
||||
j := 0
|
||||
|
||||
//for pkt := range packets {
|
||||
var cursorError error
|
||||
var pkt av.Packet
|
||||
|
||||
@@ -126,17 +66,18 @@ func ProcessMotion(motionCursor *pubsub.QueueCursor, configuration *models.Confi
|
||||
pkt, cursorError = motionCursor.ReadPacket()
|
||||
// Check If valid package.
|
||||
if len(pkt.Data) > 0 && pkt.IsKeyFrame {
|
||||
rgb := GetImage(pkt, decoder, decoderMutex)
|
||||
gray, _ := ToGray(rgb)
|
||||
matArray[j] = &gray
|
||||
j++
|
||||
grayImage, err := GetGrayImage(pkt, decoder, decoderMutex)
|
||||
if err == nil {
|
||||
imageArray[j] = grayImage
|
||||
j++
|
||||
}
|
||||
}
|
||||
if j == 2 {
|
||||
if j == 3 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
img := matArray[0]
|
||||
img := imageArray[0]
|
||||
if img != nil {
|
||||
|
||||
// Calculate mask
|
||||
@@ -155,16 +96,18 @@ func ProcessMotion(motionCursor *pubsub.QueueCursor, configuration *models.Confi
|
||||
polyObjects = append(polyObjects, poly)
|
||||
}
|
||||
|
||||
rows := img.Rows()
|
||||
cols := img.Cols()
|
||||
var coordinatesToCheck [][]int
|
||||
bounds := img.Bounds()
|
||||
rows := bounds.Dy()
|
||||
cols := bounds.Dx()
|
||||
|
||||
// Make fixed size array of uinty8
|
||||
var coordinatesToCheck []int
|
||||
for y := 0; y < rows; y++ {
|
||||
for x := 0; x < cols; x++ {
|
||||
for _, poly := range polyObjects {
|
||||
point := geo.NewPoint(float64(x), float64(y))
|
||||
if poly.Contains(point) {
|
||||
coordinatesToCheck = append(coordinatesToCheck, []int{x, y})
|
||||
break
|
||||
coordinatesToCheck = append(coordinatesToCheck, y*cols+x)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,129 +125,134 @@ func ProcessMotion(motionCursor *pubsub.QueueCursor, configuration *models.Confi
|
||||
continue
|
||||
}
|
||||
|
||||
rgb := GetImage(pkt, decoder, decoderMutex)
|
||||
gray, _ := ToGray(rgb)
|
||||
matArray[2] = &gray
|
||||
grayImage, err := GetGrayImage(pkt, decoder, decoderMutex)
|
||||
if err == nil {
|
||||
imageArray[2] = grayImage
|
||||
}
|
||||
|
||||
// Store snapshots (jpg) or hull.
|
||||
// Store snapshots (jpg) for hull.
|
||||
files, err := ioutil.ReadDir("./data/snapshots")
|
||||
if err == nil {
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
return files[i].ModTime().Before(files[j].ModTime())
|
||||
})
|
||||
if len(files) > 3 {
|
||||
os.Remove("./data/snapshots/" + files[0].Name())
|
||||
rgbImage, err := GetRawImage(pkt, decoder, decoderMutex)
|
||||
if err == nil {
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
return files[i].ModTime().Before(files[j].ModTime())
|
||||
})
|
||||
if len(files) > 3 {
|
||||
os.Remove("./data/snapshots/" + files[0].Name())
|
||||
}
|
||||
|
||||
// Save image
|
||||
t := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
f, err := os.Create("./data/snapshots/" + t + ".jpg")
|
||||
if err == nil {
|
||||
jpeg.Encode(f, &rgbImage.Image, &jpeg.Options{Quality: 15})
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
rgbImage.Free()
|
||||
}
|
||||
t := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
snapshotRGB := GetImage(pkt, decoder, decoderMutex)
|
||||
gocv.IMWrite("./data/snapshots/"+t+".png", snapshotRGB)
|
||||
snapshotRGB.Close()
|
||||
|
||||
// Check if continuous recording.
|
||||
if config.Capture.Continuous == "true" {
|
||||
// Check if within time interval
|
||||
detectMotion := true
|
||||
now := time.Now().In(loc)
|
||||
weekday := now.Weekday()
|
||||
hour := now.Hour()
|
||||
minute := now.Minute()
|
||||
second := now.Second()
|
||||
timeInterval := config.Timetable[int(weekday)]
|
||||
if timeInterval != nil {
|
||||
start1 := timeInterval.Start1
|
||||
end1 := timeInterval.End1
|
||||
start2 := timeInterval.Start2
|
||||
end2 := timeInterval.End2
|
||||
currentTimeInSeconds := hour*60*60 + minute*60 + second
|
||||
if (currentTimeInSeconds >= start1 && currentTimeInSeconds <= end1) ||
|
||||
(currentTimeInSeconds >= start2 && currentTimeInSeconds <= end2) {
|
||||
|
||||
// Do not do anything! Just sleep as there is no
|
||||
// motion detection needed
|
||||
|
||||
} else { // Do motion detection.
|
||||
|
||||
// Check if within time interval
|
||||
detectMotion := true
|
||||
now := time.Now().In(loc)
|
||||
weekday := now.Weekday()
|
||||
hour := now.Hour()
|
||||
minute := now.Minute()
|
||||
second := now.Second()
|
||||
timeInterval := config.Timetable[int(weekday)]
|
||||
if timeInterval != nil {
|
||||
start1 := timeInterval.Start1
|
||||
end1 := timeInterval.End1
|
||||
start2 := timeInterval.Start2
|
||||
end2 := timeInterval.End2
|
||||
currentTimeInSeconds := hour*60*60 + minute*60 + second
|
||||
if (currentTimeInSeconds >= start1 && currentTimeInSeconds <= end1) ||
|
||||
(currentTimeInSeconds >= start2 && currentTimeInSeconds <= end2) {
|
||||
|
||||
} else {
|
||||
detectMotion = false
|
||||
log.Log.Debug("ProcessMotion: Time interval not valid, disabling motion detection.")
|
||||
}
|
||||
}
|
||||
|
||||
// Remember additional information about the result of findmotion
|
||||
isPixelChangeThresholdReached, changesToReturn = FindMotion(matArray, coordinatesToCheck, config.Capture.PixelChangeThreshold)
|
||||
|
||||
if detectMotion && isPixelChangeThresholdReached {
|
||||
|
||||
if mqttClient != nil {
|
||||
mqttClient.Publish("kerberos/"+key+"/device/"+config.Key+"/motion", 2, false, "motion")
|
||||
}
|
||||
|
||||
//FIXME: In the future MotionDataPartial should be replaced with MotionDataFull
|
||||
dataToPass := models.MotionDataPartial{
|
||||
Timestamp: time.Now().Unix(),
|
||||
NumberOfChanges: changesToReturn,
|
||||
}
|
||||
communication.HandleMotion <- dataToPass //Save data to the channel
|
||||
} else {
|
||||
detectMotion = false
|
||||
log.Log.Debug("ProcessMotion: Time interval not valid, disabling motion detection.")
|
||||
}
|
||||
}
|
||||
|
||||
matArray[0].Close()
|
||||
matArray[0] = matArray[1]
|
||||
matArray[1] = matArray[2]
|
||||
// Remember additional information about the result of findmotion
|
||||
isPixelChangeThresholdReached, changesToReturn = FindMotion(imageArray, coordinatesToCheck, pixelThreshold)
|
||||
|
||||
if detectMotion && isPixelChangeThresholdReached {
|
||||
|
||||
if mqttClient != nil {
|
||||
mqttClient.Publish("kerberos/"+key+"/device/"+config.Key+"/motion", 2, false, "motion")
|
||||
}
|
||||
|
||||
//FIXME: In the future MotionDataPartial should be replaced with MotionDataFull
|
||||
dataToPass := models.MotionDataPartial{
|
||||
Timestamp: time.Now().Unix(),
|
||||
NumberOfChanges: changesToReturn,
|
||||
}
|
||||
communication.HandleMotion <- dataToPass //Save data to the channel
|
||||
}
|
||||
|
||||
imageArray[0] = imageArray[1]
|
||||
imageArray[1] = imageArray[2]
|
||||
i++
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
|
||||
if img != nil {
|
||||
img = nil
|
||||
}
|
||||
}
|
||||
if img != nil {
|
||||
img.Close()
|
||||
}
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
|
||||
log.Log.Debug("ProcessMotion: finished")
|
||||
}
|
||||
|
||||
func FindMotion(matArray [3]*gocv.Mat, coordinatesToCheck [][]int, pixelChangeThreshold int) (thresholdReached bool, changesDetected int) {
|
||||
func FindMotion(imageArray [3]*image.Gray, coordinatesToCheck []int, pixelChangeThreshold int) (thresholdReached bool, changesDetected int) {
|
||||
image1 := imageArray[0]
|
||||
image2 := imageArray[1]
|
||||
image3 := imageArray[2]
|
||||
threshold := 60
|
||||
changes := AbsDiffBitwiseAndThreshold(image1, image2, image3, threshold, coordinatesToCheck)
|
||||
return changes > pixelChangeThreshold, changes
|
||||
}
|
||||
|
||||
h1 := gocv.NewMat()
|
||||
gocv.AbsDiff(*matArray[2], *matArray[0], &h1)
|
||||
h2 := gocv.NewMat()
|
||||
gocv.AbsDiff(*matArray[2], *matArray[1], &h2)
|
||||
func GetGrayImage(pkt av.Packet, dec *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) (*image.Gray, error) {
|
||||
img, err := capture.DecodeImage(pkt, dec, decoderMutex)
|
||||
|
||||
and := gocv.NewMat()
|
||||
gocv.BitwiseAnd(h1, h2, &and)
|
||||
h1.Close()
|
||||
h2.Close()
|
||||
// Do a deep copy of the image
|
||||
imgDeepCopy := image.NewGray(img.ImageGray.Bounds())
|
||||
imgDeepCopy.Stride = img.ImageGray.Stride
|
||||
copy(imgDeepCopy.Pix, img.ImageGray.Pix)
|
||||
|
||||
thresh := gocv.NewMat()
|
||||
gocv.Threshold(and, &thresh, 30.0, 255.0, gocv.ThresholdBinary)
|
||||
and.Close()
|
||||
// Cleanup of underlaying data
|
||||
img.Free()
|
||||
|
||||
kernel := gocv.GetStructuringElement(gocv.MorphRect, image.Pt(3, 3))
|
||||
eroded := gocv.NewMat()
|
||||
gocv.Erode(thresh, &eroded, kernel)
|
||||
thresh.Close()
|
||||
kernel.Close()
|
||||
return imgDeepCopy, err
|
||||
}
|
||||
|
||||
func GetRawImage(pkt av.Packet, dec *ffmpeg.VideoDecoder, decoderMutex *sync.Mutex) (*ffmpeg.VideoFrame, error) {
|
||||
img, err := capture.DecodeImage(pkt, dec, decoderMutex)
|
||||
// We'll need to free up ourselves ;) using -> img.Free()
|
||||
return img, err
|
||||
}
|
||||
|
||||
func ImageToBytes(img image.Image) ([]byte, error) {
|
||||
buffer := new(bytes.Buffer)
|
||||
w := bufio.NewWriter(buffer)
|
||||
//err := jpeg.Encode(w, img, &jpeg.EncoderOptions{Quality: 70})
|
||||
err := jpeg.Encode(w, img, &jpeg.Options{Quality: 15})
|
||||
return buffer.Bytes(), err
|
||||
}
|
||||
|
||||
func AbsDiffBitwiseAndThreshold(img1 *image.Gray, img2 *image.Gray, img3 *image.Gray, threshold int, coordinatesToCheck []int) int {
|
||||
changes := 0
|
||||
for _, c := range coordinatesToCheck {
|
||||
value := eroded.GetUCharAt(c[1], c[0])
|
||||
if value > 0 {
|
||||
for i := 0; i < len(coordinatesToCheck); i++ {
|
||||
pixel := coordinatesToCheck[i]
|
||||
diff := int(img3.Pix[pixel]) - int(img1.Pix[pixel])
|
||||
diff2 := int(img3.Pix[pixel]) - int(img2.Pix[pixel])
|
||||
if (diff > threshold || diff < -threshold) && (diff2 > threshold || diff2 < -threshold) {
|
||||
changes++
|
||||
}
|
||||
}
|
||||
eroded.Close()
|
||||
|
||||
log.Log.Info("FindMotion: Number of changes detected:" + strconv.Itoa(changes))
|
||||
|
||||
if pixelChangeThreshold == 0 {
|
||||
pixelChangeThreshold = 75 // Keep hardcoded value of 75 for now if no value is given for changes treshold in config.json
|
||||
}
|
||||
|
||||
changesDetected = changes // Assign final amount of changes to the return variable
|
||||
return changes > pixelChangeThreshold, changesDetected // Return bool ifReachedThreshold AND the amount of changes detected in total
|
||||
return changes
|
||||
}
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
package models
|
||||
|
||||
type APIResponse struct {
|
||||
Data interface{} `json:"data" bson:"data"`
|
||||
Data interface{} `json:"data" bson:"data"`
|
||||
Message interface{} `json:"message" bson:"message"`
|
||||
}
|
||||
|
||||
type OnvifCredentials struct {
|
||||
ONVIFXAddr string `json:"onvif_xaddr,omitempty" bson:"onvif_xaddr"`
|
||||
ONVIFUsername string `json:"onvif_username,omitempty" bson:"onvif_username"`
|
||||
ONVIFPassword string `json:"onvif_password,omitempty" bson:"onvif_password"`
|
||||
}
|
||||
|
||||
type CameraStreams struct {
|
||||
RTSP string `json:"rtsp"`
|
||||
SubRTSP string `json:"sub_rtsp"`
|
||||
}
|
||||
|
||||
type OnvifPanTilt struct {
|
||||
OnvifCredentials OnvifCredentials `json:"onvif_credentials,omitempty" bson:"onvif_credentials"`
|
||||
Pan float64 `json:"pan,omitempty" bson:"pan"`
|
||||
Tilt float64 `json:"tilt,omitempty" bson:"tilt"`
|
||||
}
|
||||
|
||||
type OnvifZoom struct {
|
||||
OnvifCredentials OnvifCredentials `json:"onvif_credentials,omitempty" bson:"onvif_credentials"`
|
||||
Zoom float64 `json:"zoom,omitempty" bson:"zoom"`
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kerberos-io/joy4/av/pubsub"
|
||||
"github.com/kerberos-io/joy4/cgo/ffmpeg"
|
||||
"github.com/tevino/abool"
|
||||
)
|
||||
|
||||
@@ -14,6 +17,7 @@ type Communication struct {
|
||||
CloudTimestamp *atomic.Value
|
||||
HandleBootstrap chan string
|
||||
HandleStream chan string
|
||||
HandleSubStream chan string
|
||||
HandleMotion chan MotionDataPartial
|
||||
HandleUpload chan string
|
||||
HandleHeartBeat chan string
|
||||
@@ -23,4 +27,9 @@ type Communication struct {
|
||||
HandleLiveHDPeers chan string
|
||||
HandleONVIF chan OnvifAction
|
||||
IsConfiguring *abool.AtomicBool
|
||||
Queue *pubsub.Queue
|
||||
DecoderMutex *sync.Mutex
|
||||
SubDecoderMutex *sync.Mutex
|
||||
Decoder *ffmpeg.VideoDecoder
|
||||
SubDecoder *ffmpeg.VideoDecoder
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ type Config struct {
|
||||
Type string `json:"type"`
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
FriendlyName string `json:"friendly_name"`
|
||||
Time string `json:"time" bson:"time"`
|
||||
Offline string `json:"offline"`
|
||||
AutoClean string `json:"auto_clean"`
|
||||
@@ -48,6 +49,7 @@ type Capture struct {
|
||||
IPCamera IPCamera `json:"ipcamera"`
|
||||
USBCamera USBCamera `json:"usbcamera"`
|
||||
RaspiCamera RaspiCamera `json:"raspicamera"`
|
||||
Recording string `json:"recording,omitempty"`
|
||||
Continuous string `json:"continuous,omitempty"`
|
||||
PostRecording int64 `json:"postrecording"`
|
||||
PreRecording int64 `json:"prerecording"`
|
||||
@@ -136,7 +138,7 @@ type S3 struct {
|
||||
// By defining KStorage you can make your recordings available in the cloud, at a centrel place.
|
||||
type KStorage struct {
|
||||
URI string `json:"uri,omitempty" bson:"uri,omitempty"`
|
||||
CloudKey string `json:"cloud_key,omitempty" bson:"cloud_key,omitempty"`
|
||||
CloudKey string `json:"cloud_key,omitempty" bson:"cloud_key,omitempty"` /* old way, remove this */
|
||||
AccessKey string `json:"access_key,omitempty" bson:"access_key,omitempty"`
|
||||
SecretAccessKey string `json:"secret_access_key,omitempty" bson:"secret_access_key,omitempty"`
|
||||
Provider string `json:"provider,omitempty" bson:"provider,omitempty"`
|
||||
|
||||
@@ -6,9 +6,10 @@ type OnvifAction struct {
|
||||
}
|
||||
|
||||
type OnvifActionPTZ struct {
|
||||
Left int `json:"left" bson:"left"`
|
||||
Right int `json:"right" bson:"right"`
|
||||
Up int `json:"up" bson:"up"`
|
||||
Down int `json:"down" bson:"down"`
|
||||
Center int `json:"center" bson:"center"`
|
||||
Left int `json:"left" bson:"left"`
|
||||
Right int `json:"right" bson:"right"`
|
||||
Up int `json:"up" bson:"up"`
|
||||
Down int `json:"down" bson:"down"`
|
||||
Center int `json:"center" bson:"center"`
|
||||
Zoom float64 `json:"zoom" bson:"zoom"`
|
||||
}
|
||||
|
||||
16
machinery/src/models/System.go
Normal file
16
machinery/src/models/System.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
type System struct {
|
||||
CPUId string `json:"cpu_idle" bson:"cpu_idle"`
|
||||
Hostname string `json:"hostname" bson:"hostname"`
|
||||
Version string `json:"version" bson:"version"`
|
||||
Release string `json:"release" bson:"release"`
|
||||
BootTime uint64 `json:"boot_time" bson:"boot_time"`
|
||||
KernelVersion string `json:"kernel_version" bson:"kernel_version"`
|
||||
MACs []string `json:"macs" bson:"macs"`
|
||||
IPs []string `json:"ips" bson:"ips"`
|
||||
Architecture string `json:"architecture" bson:"architecture"`
|
||||
UsedMemory uint64 `json:"used_memory" bson:"used_memory"`
|
||||
TotalMemory uint64 `json:"total_memory" bson:"total_memory"`
|
||||
FreeMemory uint64 `json:"free_memory" bson:"free_memory"`
|
||||
}
|
||||
@@ -7,3 +7,16 @@ type User struct {
|
||||
Role string `json:"role" bson:"role"`
|
||||
Language string `json:"language" bson:"language"`
|
||||
}
|
||||
|
||||
type Authentication struct {
|
||||
Username string `json:"username" bson:"username"`
|
||||
Password string `json:"password" bson:"password"`
|
||||
}
|
||||
|
||||
type Authorization struct {
|
||||
Code int `json:"code" bson:"code"`
|
||||
Token string `json:"token" bson:"token"`
|
||||
Expire string `json:"expire" bson:"expire"`
|
||||
Username string `json:"username" bson:"username"`
|
||||
Role string `json:"role" bson:"role"`
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kerberos-io/agent/machinery/src/log"
|
||||
"github.com/kerberos-io/agent/machinery/src/models"
|
||||
@@ -19,97 +22,77 @@ import (
|
||||
func HandleONVIFActions(configuration *models.Configuration, communication *models.Communication) {
|
||||
log.Log.Debug("HandleONVIFActions: started")
|
||||
|
||||
config := configuration.Config
|
||||
for onvifAction := range communication.HandleONVIF {
|
||||
|
||||
// First we'll get the desired PTZ action from the payload
|
||||
// We need to know if we need to move left, right, up, down, zoom in, zoom out, center.
|
||||
var ptzAction models.OnvifActionPTZ
|
||||
b, _ := json.Marshal(onvifAction.Payload)
|
||||
json.Unmarshal(b, &ptzAction)
|
||||
|
||||
if onvifAction.Action == "ptz" {
|
||||
|
||||
// Do the PTZ shizzle
|
||||
device, err := onvif.NewDevice(onvif.DeviceParams{
|
||||
Xaddr: config.Capture.IPCamera.ONVIFXAddr,
|
||||
Username: config.Capture.IPCamera.ONVIFUsername,
|
||||
Password: config.Capture.IPCamera.ONVIFPassword,
|
||||
})
|
||||
|
||||
//services := device.GetServices()
|
||||
//fmt.Println(services)
|
||||
|
||||
// Get Profiles
|
||||
resp, _ := device.CallMethod(media.GetProfiles{})
|
||||
defer resp.Body.Close()
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
stringBody := string(b)
|
||||
decodedXML, et, errorFunc := getXMLNode(stringBody, "GetProfilesResponse")
|
||||
if errorFunc != "" {
|
||||
}
|
||||
var mProfilesResp media.GetProfilesResponse
|
||||
if err := decodedXML.DecodeElement(&mProfilesResp, et); err != nil {
|
||||
log.Log.Error("HandleONVIFActions: " + err.Error())
|
||||
}
|
||||
var ptzToken xsd.ReferenceToken
|
||||
for _, profile := range mProfilesResp.Profiles {
|
||||
ptzToken = profile.Token
|
||||
}
|
||||
// Connect to Onvif device
|
||||
device, err := ConnectToOnvifDevice(configuration)
|
||||
if err == nil {
|
||||
|
||||
// Get token from the first profile
|
||||
token, err := GetTokenFromProfile(device, 0)
|
||||
if err == nil {
|
||||
|
||||
if ptzAction.Center == 1 {
|
||||
absoluteVector := xsd.Vector2D{
|
||||
X: 0,
|
||||
Y: 0,
|
||||
Space: "http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace",
|
||||
}
|
||||
// Get the configurations from the device
|
||||
configurations, err := GetConfigurationsFromDevice(device)
|
||||
|
||||
res, _ := device.CallMethod(ptz.AbsoluteMove{
|
||||
ProfileToken: ptzToken,
|
||||
Position: xsd.PTZVector{
|
||||
PanTilt: absoluteVector,
|
||||
Zoom: xsd.Vector1D{
|
||||
X: 0,
|
||||
Space: "http://www.onvif.org/ver10/tptz/ZoomSpaces/PositionGenericSpace",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
|
||||
bs, _ := ioutil.ReadAll(res.Body)
|
||||
log.Log.Info("HandleONVIFActions: " + string(bs))
|
||||
if onvifAction.Action == "ptz" {
|
||||
|
||||
} else {
|
||||
distance := 0.1
|
||||
panTiltVector := xsd.Vector2D{
|
||||
X: 0,
|
||||
Y: 0,
|
||||
Space: "http://www.onvif.org/ver10/tptz/PanTiltSpaces/TranslationGenericSpace",
|
||||
}
|
||||
if ptzAction.Left == 1 {
|
||||
panTiltVector.X = -1 * distance
|
||||
}
|
||||
if ptzAction.Right == 1 {
|
||||
panTiltVector.X = distance
|
||||
}
|
||||
if ptzAction.Up == 1 {
|
||||
panTiltVector.Y = distance
|
||||
}
|
||||
if ptzAction.Down == 1 {
|
||||
panTiltVector.Y = -1 * distance
|
||||
}
|
||||
if err == nil {
|
||||
|
||||
res, _ := device.CallMethod(ptz.RelativeMove{
|
||||
ProfileToken: ptzToken,
|
||||
Translation: xsd.PTZVector{
|
||||
PanTilt: panTiltVector,
|
||||
Zoom: xsd.Vector1D{
|
||||
X: 0,
|
||||
Space: "http://www.onvif.org/ver10/tptz/ZoomSpaces/TranslationGenericSpace",
|
||||
},
|
||||
},
|
||||
})
|
||||
if ptzAction.Center == 1 {
|
||||
|
||||
bs, _ := ioutil.ReadAll(res.Body)
|
||||
log.Log.Info("HandleONVIFActions: " + string(bs))
|
||||
// We will move the camera to zero position.
|
||||
err := AbsolutePanTiltMove(device, configurations, token, 0, 0)
|
||||
if err != nil {
|
||||
log.Log.Error("HandleONVIFActions (AbsolutePanTitleMove): " + err.Error())
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Distance should be a parameter as well
|
||||
distance := 0.7
|
||||
|
||||
// We will calculate if we need to move pan or tilt (and the direction).
|
||||
x := float64(0)
|
||||
y := float64(0)
|
||||
|
||||
if ptzAction.Left == 1 {
|
||||
x = -1 * distance
|
||||
}
|
||||
if ptzAction.Right == 1 {
|
||||
x = 1 * distance
|
||||
}
|
||||
if ptzAction.Up == 1 {
|
||||
y = 1 * distance
|
||||
}
|
||||
if ptzAction.Down == 1 {
|
||||
y = -1 * distance
|
||||
}
|
||||
|
||||
err := ContinuousPanTilt(device, configurations, token, x, y)
|
||||
if err != nil {
|
||||
log.Log.Error("HandleONVIFActions (ContinuousPanTilt): " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if onvifAction.Action == "zoom" {
|
||||
|
||||
if err == nil {
|
||||
zoom := ptzAction.Zoom
|
||||
err := ContinuousZoom(device, configurations, token, zoom)
|
||||
if err != nil {
|
||||
log.Log.Error("HandleONVIFActions (ContinuousZoom): " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,11 +100,209 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode
|
||||
log.Log.Debug("HandleONVIFActions: finished")
|
||||
}
|
||||
|
||||
func getXMLNode(xmlBody string, nodeName string) (*xml.Decoder, *xml.StartElement, string) {
|
||||
func ConnectToOnvifDevice(configuration *models.Configuration) (*onvif.Device, error) {
|
||||
log.Log.Debug("ConnectToOnvifDevice: started")
|
||||
|
||||
config := configuration.Config
|
||||
|
||||
// Get the capabilities of the ONVIF device
|
||||
device, err := onvif.NewDevice(onvif.DeviceParams{
|
||||
Xaddr: config.Capture.IPCamera.ONVIFXAddr,
|
||||
Username: config.Capture.IPCamera.ONVIFUsername,
|
||||
Password: config.Capture.IPCamera.ONVIFPassword,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Log.Error("ConnectToOnvifDevice: " + err.Error())
|
||||
}
|
||||
|
||||
log.Log.Debug("ConnectToOnvifDevice: finished")
|
||||
return device, err
|
||||
}
|
||||
|
||||
func GetTokenFromProfile(device *onvif.Device, profileId int) (xsd.ReferenceToken, error) {
|
||||
// We aim to receive a profile token from the server
|
||||
var profileToken xsd.ReferenceToken
|
||||
|
||||
// Get Profiles
|
||||
resp, err := device.CallMethod(media.GetProfiles{})
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
stringBody := string(b)
|
||||
decodedXML, et, err := getXMLNode(stringBody, "GetProfilesResponse")
|
||||
if err != nil {
|
||||
log.Log.Error("GetTokenFromProfile: " + err.Error())
|
||||
return profileToken, err
|
||||
} else {
|
||||
// Decode the profiles from the server
|
||||
var mProfilesResp media.GetProfilesResponse
|
||||
if err := decodedXML.DecodeElement(&mProfilesResp, et); err != nil {
|
||||
log.Log.Error("GetTokenFromProfile: " + err.Error())
|
||||
}
|
||||
|
||||
// We'll try to get the token from a preferred profile
|
||||
for i, profile := range mProfilesResp.Profiles {
|
||||
if profileId == i {
|
||||
profileToken = profile.Token
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return profileToken, err
|
||||
}
|
||||
|
||||
func GetConfigurationsFromDevice(device *onvif.Device) (ptz.GetConfigurationsResponse, error) {
|
||||
// We'll try to receive the PTZ configurations from the server
|
||||
var configurations ptz.GetConfigurationsResponse
|
||||
|
||||
// Get the configurations from the device
|
||||
resp, err := device.CallMethod(ptz.GetConfigurations{})
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
stringBody := string(b)
|
||||
decodedXML, et, err := getXMLNode(stringBody, "GetConfigurationsResponse")
|
||||
if err != nil {
|
||||
log.Log.Error("GetConfigurationsFromDevice: " + err.Error())
|
||||
return configurations, err
|
||||
} else {
|
||||
if err := decodedXML.DecodeElement(&configurations, et); err != nil {
|
||||
log.Log.Error("GetConfigurationsFromDevice: " + err.Error())
|
||||
return configurations, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return configurations, err
|
||||
}
|
||||
|
||||
func AbsolutePanTiltMove(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsd.ReferenceToken, pan float32, tilt float32) error {
|
||||
|
||||
absoluteVector := xsd.Vector2D{
|
||||
X: float64(pan),
|
||||
Y: float64(tilt),
|
||||
Space: configuration.PTZConfiguration.DefaultAbsolutePantTiltPositionSpace,
|
||||
}
|
||||
|
||||
res, err := device.CallMethod(ptz.AbsoluteMove{
|
||||
ProfileToken: token,
|
||||
Position: xsd.PTZVector{
|
||||
PanTilt: absoluteVector,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Log.Error("AbsoluteMove: " + err.Error())
|
||||
}
|
||||
|
||||
bs, _ := ioutil.ReadAll(res.Body)
|
||||
log.Log.Debug("AbsoluteMove: " + string(bs))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func ContinuousPanTilt(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsd.ReferenceToken, pan float64, tilt float64) error {
|
||||
|
||||
panTiltVector := xsd.Vector2D{
|
||||
X: pan,
|
||||
Y: tilt,
|
||||
Space: configuration.PTZConfiguration.DefaultContinuousPanTiltVelocitySpace,
|
||||
}
|
||||
|
||||
res, err := device.CallMethod(ptz.ContinuousMove{
|
||||
ProfileToken: token,
|
||||
Velocity: xsd.PTZSpeedPanTilt{
|
||||
PanTilt: panTiltVector,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Log.Error("ContinuousPanTiltMove: " + err.Error())
|
||||
}
|
||||
|
||||
bs, _ := ioutil.ReadAll(res.Body)
|
||||
log.Log.Debug("ContinuousPanTiltMove: " + string(bs))
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
res, errStop := device.CallMethod(ptz.Stop{
|
||||
ProfileToken: token,
|
||||
PanTilt: true,
|
||||
})
|
||||
|
||||
if errStop != nil {
|
||||
log.Log.Error("ContinuousPanTiltMove: " + errStop.Error())
|
||||
}
|
||||
|
||||
if errStop == nil {
|
||||
return err
|
||||
} else {
|
||||
return errStop
|
||||
}
|
||||
}
|
||||
|
||||
func ContinuousZoom(device *onvif.Device, configuration ptz.GetConfigurationsResponse, token xsd.ReferenceToken, zoom float64) error {
|
||||
|
||||
zoomVector := xsd.Vector1D{
|
||||
X: zoom,
|
||||
Space: configuration.PTZConfiguration.DefaultContinuousZoomVelocitySpace,
|
||||
}
|
||||
|
||||
res, err := device.CallMethod(ptz.ContinuousMove{
|
||||
ProfileToken: token,
|
||||
Velocity: xsd.PTZSpeedZoom{
|
||||
Zoom: zoomVector,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Log.Error("ContinuousPanTiltZoom: " + err.Error())
|
||||
}
|
||||
|
||||
bs, _ := ioutil.ReadAll(res.Body)
|
||||
log.Log.Debug("ContinuousPanTiltZoom: " + string(bs))
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
res, errStop := device.CallMethod(ptz.Stop{
|
||||
ProfileToken: token,
|
||||
Zoom: true,
|
||||
})
|
||||
|
||||
if errStop != nil {
|
||||
log.Log.Error("ContinuousPanTiltZoom: " + errStop.Error())
|
||||
}
|
||||
|
||||
if errStop == nil {
|
||||
return err
|
||||
} else {
|
||||
return errStop
|
||||
}
|
||||
}
|
||||
|
||||
func GetCapabilitiesFromDevice(device *onvif.Device) []string {
|
||||
var capabilities []string
|
||||
services := device.GetServices()
|
||||
for key, _ := range services {
|
||||
log.Log.Debug("GetCapabilitiesFromDevice: has key: " + key)
|
||||
if key != "" {
|
||||
keyParts := strings.Split(key, "/")
|
||||
if len(keyParts) > 0 {
|
||||
capability := keyParts[len(keyParts)-1]
|
||||
capabilities = append(capabilities, capability)
|
||||
}
|
||||
}
|
||||
}
|
||||
return capabilities
|
||||
}
|
||||
|
||||
func getXMLNode(xmlBody string, nodeName string) (*xml.Decoder, *xml.StartElement, error) {
|
||||
xmlBytes := bytes.NewBufferString(xmlBody)
|
||||
decodedXML := xml.NewDecoder(xmlBytes)
|
||||
|
||||
for {
|
||||
token, err := decodedXML.Token()
|
||||
if err != nil {
|
||||
@@ -130,9 +311,9 @@ func getXMLNode(xmlBody string, nodeName string) (*xml.Decoder, *xml.StartElemen
|
||||
switch et := token.(type) {
|
||||
case xml.StartElement:
|
||||
if et.Name.Local == nodeName {
|
||||
return decodedXML, &et, ""
|
||||
return decodedXML, &et, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil, "error in NodeName"
|
||||
return nil, nil, errors.New("error in NodeName - username and password might be wrong")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
jwt "github.com/appleboy/gin-jwt/v2"
|
||||
@@ -45,9 +46,18 @@ func JWTMiddleWare() jwt.GinJWTMiddleware {
|
||||
username := loginVals.Username
|
||||
password := loginVals.Password
|
||||
|
||||
usernameENV := "root"
|
||||
passwordENV := "root"
|
||||
if username == usernameENV && password == passwordENV {
|
||||
// Get username from ENV
|
||||
usernameFromConfig := os.Getenv("AGENT_USERNAME")
|
||||
if usernameFromConfig == "" {
|
||||
usernameFromConfig = "root"
|
||||
}
|
||||
// Get password from ENV
|
||||
passwordFromConfig := os.Getenv("AGENT_PASSWORD")
|
||||
if passwordFromConfig == "" {
|
||||
passwordFromConfig = "root"
|
||||
}
|
||||
|
||||
if username == usernameFromConfig && password == passwordFromConfig {
|
||||
return &models.User{
|
||||
Username: username,
|
||||
Role: "admin",
|
||||
|
||||
248
machinery/src/routers/http/Methods.go
Normal file
248
machinery/src/routers/http/Methods.go
Normal file
@@ -0,0 +1,248 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/kerberos-io/agent/machinery/src/models"
|
||||
"github.com/kerberos-io/agent/machinery/src/onvif"
|
||||
)
|
||||
|
||||
// Login godoc
|
||||
// @Router /api/login [post]
|
||||
// @ID login
|
||||
// @Tags authentication
|
||||
// @Summary Get Authorization token.
|
||||
// @Description Get Authorization token.
|
||||
// @Param credentials body models.Authentication true "Credentials"
|
||||
// @Success 200 {object} models.Authorization
|
||||
func Login() {}
|
||||
|
||||
// LoginToOnvif godoc
|
||||
// @Router /api/camera/onvif/login [post]
|
||||
// @ID camera-onvif-login
|
||||
// @Tags camera
|
||||
// @Param config body models.OnvifCredentials true "OnvifCredentials"
|
||||
// @Summary Try to login into ONVIF supported camera.
|
||||
// @Description Try to login into ONVIF supported camera.
|
||||
// @Success 200 {object} models.APIResponse
|
||||
func LoginToOnvif(c *gin.Context) {
|
||||
var onvifCredentials models.OnvifCredentials
|
||||
err := c.BindJSON(&onvifCredentials)
|
||||
|
||||
if err == nil && onvifCredentials.ONVIFXAddr != "" {
|
||||
|
||||
configuration := &models.Configuration{
|
||||
Config: models.Config{
|
||||
Capture: models.Capture{
|
||||
IPCamera: models.IPCamera{
|
||||
ONVIFXAddr: onvifCredentials.ONVIFXAddr,
|
||||
ONVIFUsername: onvifCredentials.ONVIFUsername,
|
||||
ONVIFPassword: onvifCredentials.ONVIFPassword,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
device, err := onvif.ConnectToOnvifDevice(configuration)
|
||||
if err == nil {
|
||||
c.JSON(200, gin.H{
|
||||
"device": device,
|
||||
})
|
||||
} else {
|
||||
c.JSON(400, gin.H{
|
||||
"data": "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
c.JSON(400, gin.H{
|
||||
"data": "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetOnvifCapabilities godoc
|
||||
// @Router /api/camera/onvif/capabilities [post]
|
||||
// @ID camera-onvif-capabilities
|
||||
// @Tags camera
|
||||
// @Param config body models.OnvifCredentials true "OnvifCredentials"
|
||||
// @Summary Will return the ONVIF capabilities for the specific camera.
|
||||
// @Description Will return the ONVIF capabilities for the specific camera.
|
||||
// @Success 200 {object} models.APIResponse
|
||||
func GetOnvifCapabilities(c *gin.Context) {
|
||||
var onvifCredentials models.OnvifCredentials
|
||||
err := c.BindJSON(&onvifCredentials)
|
||||
|
||||
if err == nil && onvifCredentials.ONVIFXAddr != "" {
|
||||
|
||||
configuration := &models.Configuration{
|
||||
Config: models.Config{
|
||||
Capture: models.Capture{
|
||||
IPCamera: models.IPCamera{
|
||||
ONVIFXAddr: onvifCredentials.ONVIFXAddr,
|
||||
ONVIFUsername: onvifCredentials.ONVIFUsername,
|
||||
ONVIFPassword: onvifCredentials.ONVIFPassword,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
device, err := onvif.ConnectToOnvifDevice(configuration)
|
||||
if err == nil {
|
||||
c.JSON(200, gin.H{
|
||||
"capabilities": onvif.GetCapabilitiesFromDevice(device),
|
||||
})
|
||||
} else {
|
||||
c.JSON(400, gin.H{
|
||||
"data": "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
c.JSON(400, gin.H{
|
||||
"data": "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// DoOnvifPanTilt godoc
|
||||
// @Router /api/camera/onvif/pantilt [post]
|
||||
// @ID camera-onvif-pantilt
|
||||
// @Tags camera
|
||||
// @Param panTilt body models.OnvifPanTilt true "OnvifPanTilt"
|
||||
// @Summary Panning or/and tilting the camera.
|
||||
// @Description Panning or/and tilting the camera using a direction (x,y).
|
||||
// @Success 200 {object} models.APIResponse
|
||||
func DoOnvifPanTilt(c *gin.Context) {
|
||||
var onvifPanTilt models.OnvifPanTilt
|
||||
err := c.BindJSON(&onvifPanTilt)
|
||||
|
||||
if err == nil && onvifPanTilt.OnvifCredentials.ONVIFXAddr != "" {
|
||||
|
||||
configuration := &models.Configuration{
|
||||
Config: models.Config{
|
||||
Capture: models.Capture{
|
||||
IPCamera: models.IPCamera{
|
||||
ONVIFXAddr: onvifPanTilt.OnvifCredentials.ONVIFXAddr,
|
||||
ONVIFUsername: onvifPanTilt.OnvifCredentials.ONVIFUsername,
|
||||
ONVIFPassword: onvifPanTilt.OnvifCredentials.ONVIFPassword,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
device, err := onvif.ConnectToOnvifDevice(configuration)
|
||||
|
||||
if err == nil {
|
||||
// Get token from the first profile
|
||||
token, err := onvif.GetTokenFromProfile(device, 0)
|
||||
|
||||
if err == nil {
|
||||
|
||||
// Get the configurations from the device
|
||||
configurations, err := onvif.GetConfigurationsFromDevice(device)
|
||||
|
||||
if err == nil {
|
||||
|
||||
pan := onvifPanTilt.Pan
|
||||
tilt := onvifPanTilt.Tilt
|
||||
err := onvif.ContinuousPanTilt(device, configurations, token, pan, tilt)
|
||||
if err == nil {
|
||||
c.JSON(200, models.APIResponse{
|
||||
Message: "Successfully pan/tilted the camera",
|
||||
})
|
||||
} else {
|
||||
c.JSON(400, models.APIResponse{
|
||||
Message: "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
c.JSON(400, models.APIResponse{
|
||||
Message: "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
c.JSON(400, models.APIResponse{
|
||||
Message: "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
c.JSON(400, models.APIResponse{
|
||||
Message: "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
c.JSON(400, models.APIResponse{
|
||||
Message: "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// DoOnvifZoom godoc
|
||||
// @Router /api/camera/onvif/zoom [post]
|
||||
// @ID camera-onvif-zoom
|
||||
// @Tags camera
|
||||
// @Param zoom body models.OnvifZoom true "OnvifZoom"
|
||||
// @Summary Zooming in or out the camera.
|
||||
// @Description Zooming in or out the camera.
|
||||
// @Success 200 {object} models.APIResponse
|
||||
func DoOnvifZoom(c *gin.Context) {
|
||||
var onvifZoom models.OnvifZoom
|
||||
err := c.BindJSON(&onvifZoom)
|
||||
|
||||
if err == nil && onvifZoom.OnvifCredentials.ONVIFXAddr != "" {
|
||||
|
||||
configuration := &models.Configuration{
|
||||
Config: models.Config{
|
||||
Capture: models.Capture{
|
||||
IPCamera: models.IPCamera{
|
||||
ONVIFXAddr: onvifZoom.OnvifCredentials.ONVIFXAddr,
|
||||
ONVIFUsername: onvifZoom.OnvifCredentials.ONVIFUsername,
|
||||
ONVIFPassword: onvifZoom.OnvifCredentials.ONVIFPassword,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
device, err := onvif.ConnectToOnvifDevice(configuration)
|
||||
|
||||
if err == nil {
|
||||
// Get token from the first profile
|
||||
token, err := onvif.GetTokenFromProfile(device, 0)
|
||||
|
||||
if err == nil {
|
||||
|
||||
// Get the configurations from the device
|
||||
configurations, err := onvif.GetConfigurationsFromDevice(device)
|
||||
|
||||
if err == nil {
|
||||
|
||||
zoom := onvifZoom.Zoom
|
||||
err := onvif.ContinuousZoom(device, configurations, token, zoom)
|
||||
if err == nil {
|
||||
c.JSON(200, models.APIResponse{
|
||||
Message: "Successfully zoomed the camera",
|
||||
})
|
||||
} else {
|
||||
c.JSON(400, models.APIResponse{
|
||||
Message: "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
c.JSON(400, models.APIResponse{
|
||||
Message: "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
c.JSON(400, models.APIResponse{
|
||||
Message: "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
c.JSON(400, models.APIResponse{
|
||||
Message: "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
c.JSON(400, models.APIResponse{
|
||||
Message: "Something went wrong: " + err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
|
||||
jwt "github.com/appleboy/gin-jwt/v2"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/kerberos-io/agent/machinery/src/capture"
|
||||
"github.com/kerberos-io/agent/machinery/src/routers/websocket"
|
||||
|
||||
"github.com/kerberos-io/agent/machinery/src/cloud"
|
||||
"github.com/kerberos-io/agent/machinery/src/components"
|
||||
@@ -16,6 +18,10 @@ import (
|
||||
|
||||
func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configuration *models.Configuration, communication *models.Communication) *gin.RouterGroup {
|
||||
|
||||
r.GET("/ws", func(c *gin.Context) {
|
||||
websocket.WebsocketHandler(c, communication)
|
||||
})
|
||||
|
||||
// This is legacy should be removed in future! Now everything
|
||||
// lives under the /api prefix.
|
||||
r.GET("/config", func(c *gin.Context) {
|
||||
@@ -73,7 +79,7 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configuratio
|
||||
|
||||
// The total number of recordings stored in the directory.
|
||||
recordingDirectory := "./data/recordings"
|
||||
numberOfRecordings := utils.NumberOfFilesInDirectory(recordingDirectory)
|
||||
numberOfRecordings := utils.NumberOfMP4sInDirectory(recordingDirectory)
|
||||
|
||||
// All days stored in this agent.
|
||||
days := []string{}
|
||||
@@ -146,20 +152,6 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configuratio
|
||||
}
|
||||
})
|
||||
|
||||
// Streaming handler
|
||||
api.GET("/stream", func(c *gin.Context) {
|
||||
// TODO add a token validation!
|
||||
imageFunction := func() (image.Image, error) {
|
||||
// We will only send an image once per second.
|
||||
time.Sleep(time.Second * 1)
|
||||
log.Log.Info("AddRoutes (/stream): reading from MJPEG stream")
|
||||
img, err := components.GetImageFromFilePath()
|
||||
return img, err
|
||||
}
|
||||
h := components.StartMotionJPEG(imageFunction, 80)
|
||||
h.ServeHTTP(c.Writer, c.Request)
|
||||
})
|
||||
|
||||
api.GET("/config", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"config": configuration.Config,
|
||||
@@ -212,10 +204,32 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configuratio
|
||||
cloud.VerifyPersistence(c)
|
||||
})
|
||||
|
||||
// Streaming handler
|
||||
api.GET("/stream", func(c *gin.Context) {
|
||||
// TODO add a token validation!
|
||||
imageFunction := func() (image.Image, error) {
|
||||
// We will only send an image once per second.
|
||||
time.Sleep(time.Second * 1)
|
||||
log.Log.Info("AddRoutes (/stream): reading from MJPEG stream")
|
||||
img, err := components.GetImageFromFilePath()
|
||||
return img, err
|
||||
}
|
||||
h := components.StartMotionJPEG(imageFunction, 80)
|
||||
h.ServeHTTP(c.Writer, c.Request)
|
||||
})
|
||||
|
||||
// Camera specific methods. Doesn't require any authorization.
|
||||
// These are available for anyone, but require the agent, to reach
|
||||
// the camera.
|
||||
api.POST("/camera/onvif/login", LoginToOnvif)
|
||||
api.POST("/camera/onvif/capabilities", GetOnvifCapabilities)
|
||||
api.POST("/camera/onvif/pantilt", DoOnvifPanTilt)
|
||||
api.POST("/camera/onvif/zoom", DoOnvifZoom)
|
||||
api.POST("/camera/verify/:streamType", capture.VerifyCamera)
|
||||
|
||||
// Secured endpoints..
|
||||
api.Use(authMiddleware.MiddlewareFunc())
|
||||
{
|
||||
// Secured endpoints..
|
||||
|
||||
}
|
||||
}
|
||||
return api
|
||||
|
||||
@@ -63,7 +63,7 @@ func StartServer(configuration *models.Configuration, communication *models.Comm
|
||||
r.Use(static.Serve("/media", static.LocalFile("./www", true)))
|
||||
r.Use(static.Serve("/settings", static.LocalFile("./www", true)))
|
||||
r.Use(static.Serve("/login", static.LocalFile("./www", true)))
|
||||
r.StaticFS("/file", gin.Dir("./data/recordings", false))
|
||||
r.Handle("GET", "/file/*filepath", Files)
|
||||
|
||||
// Run the api on port
|
||||
err = r.Run(":" + configuration.Port)
|
||||
@@ -71,3 +71,9 @@ func StartServer(configuration *models.Configuration, communication *models.Comm
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Files(c *gin.Context) {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Content-Type", "video/mp4")
|
||||
c.File("./data/recordings" + c.Param("filepath"))
|
||||
}
|
||||
|
||||
173
machinery/src/routers/websocket/main.go
Normal file
173
machinery/src/routers/websocket/main.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/kerberos-io/agent/machinery/src/computervision"
|
||||
"github.com/kerberos-io/agent/machinery/src/log"
|
||||
"github.com/kerberos-io/agent/machinery/src/models"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
ClientID string `json:"client_id" bson:"client_id"`
|
||||
MessageType string `json:"message_type" bson:"message_type"`
|
||||
Message map[string]string `json:"message" bson:"message"`
|
||||
}
|
||||
|
||||
type Connection struct {
|
||||
Socket *websocket.Conn
|
||||
mu sync.Mutex
|
||||
Cancels map[string]context.CancelFunc
|
||||
}
|
||||
|
||||
// Concurrency handling - sending messages
|
||||
func (c *Connection) WriteJson(message Message) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.Socket.WriteJSON(message)
|
||||
}
|
||||
|
||||
func (c *Connection) WriteMessage(bytes []byte) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.Socket.WriteMessage(websocket.TextMessage, bytes)
|
||||
}
|
||||
|
||||
var sockets = make(map[string]*Connection)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
func WebsocketHandler(c *gin.Context, communication *models.Communication) {
|
||||
w := c.Writer
|
||||
r := c.Request
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
// error handling here
|
||||
if err == nil {
|
||||
defer conn.Close()
|
||||
|
||||
var message Message
|
||||
err = conn.ReadJSON(&message)
|
||||
clientID := message.ClientID
|
||||
if sockets[clientID] == nil {
|
||||
connection := new(Connection)
|
||||
connection.Socket = conn
|
||||
sockets[clientID] = connection
|
||||
sockets[clientID].Cancels = make(map[string]context.CancelFunc)
|
||||
}
|
||||
|
||||
// Continuously read messages
|
||||
for {
|
||||
switch message.MessageType {
|
||||
case "hello":
|
||||
m := message.Message
|
||||
bePolite := Message{
|
||||
ClientID: clientID,
|
||||
MessageType: "hello-back",
|
||||
Message: map[string]string{
|
||||
"message": "Hello " + m["client_id"] + "!",
|
||||
},
|
||||
}
|
||||
sockets[clientID].WriteJson(bePolite)
|
||||
|
||||
case "stop-sd":
|
||||
_, exists := sockets[clientID].Cancels["stream-sd"]
|
||||
if exists {
|
||||
sockets[clientID].Cancels["stream-sd"]()
|
||||
delete(sockets[clientID].Cancels, "stream-sd")
|
||||
} else {
|
||||
fmt.Println("Streaming sd does not exists for " + clientID)
|
||||
}
|
||||
|
||||
case "stream-sd":
|
||||
startStrean := Message{
|
||||
ClientID: clientID,
|
||||
MessageType: "stream-sd",
|
||||
Message: map[string]string{
|
||||
"message": "Start streaming low resolution",
|
||||
},
|
||||
}
|
||||
sockets[clientID].WriteJson(startStrean)
|
||||
|
||||
_, exists := sockets[clientID].Cancels["stream-sd"]
|
||||
if exists {
|
||||
fmt.Println("Already streaming sd for " + clientID)
|
||||
} else {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
sockets[clientID].Cancels["stream-sd"] = cancel
|
||||
go ForwardSDStream(ctx, clientID, sockets[clientID], communication)
|
||||
}
|
||||
}
|
||||
|
||||
err = conn.ReadJSON(&message)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
// If clientID is in sockets
|
||||
_, exists := sockets[clientID]
|
||||
if exists {
|
||||
delete(sockets, clientID)
|
||||
log.Log.Info("WebsocketHandler: " + clientID + ": terminated and disconnected websocket connection.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ForwardSDStream(ctx context.Context, clientID string, connection *Connection, communication *models.Communication) {
|
||||
|
||||
queue := communication.Queue
|
||||
cursor := queue.Latest()
|
||||
decoder := communication.Decoder
|
||||
decoderMutex := communication.DecoderMutex
|
||||
|
||||
logreader:
|
||||
for {
|
||||
var encodedImage string
|
||||
if cursor != nil && decoder != nil {
|
||||
pkt, err := cursor.ReadPacket()
|
||||
if err == nil {
|
||||
if !pkt.IsKeyFrame {
|
||||
continue
|
||||
}
|
||||
img, err := computervision.GetRawImage(pkt, decoder, decoderMutex)
|
||||
if err == nil {
|
||||
bytes, _ := computervision.ImageToBytes(&img.Image)
|
||||
encodedImage = base64.StdEncoding.EncodeToString(bytes)
|
||||
}
|
||||
// Cleanup the image.
|
||||
img.Free()
|
||||
} else {
|
||||
log.Log.Error("ForwardSDStream:" + err.Error())
|
||||
break logreader
|
||||
}
|
||||
}
|
||||
|
||||
startStrean := Message{
|
||||
ClientID: clientID,
|
||||
MessageType: "image",
|
||||
Message: map[string]string{
|
||||
"base64": encodedImage,
|
||||
},
|
||||
}
|
||||
err := connection.WriteJson(startStrean)
|
||||
if err != nil {
|
||||
log.Log.Error("ForwardSDStream:" + err.Error())
|
||||
break logreader
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
break logreader
|
||||
default:
|
||||
}
|
||||
}
|
||||
log.Log.Info("ForwardSDStream: stop sending streaming over websocket")
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
@@ -19,6 +20,18 @@ import (
|
||||
|
||||
const letterBytes = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
// MaxUint8 - maximum value which can be held in an uint8
|
||||
const MaxUint8 = ^uint8(0)
|
||||
|
||||
// MinUint8 - minimum value which can be held in an uint8
|
||||
const MinUint8 = 0
|
||||
|
||||
// MaxUint16 - maximum value which can be held in an uint16
|
||||
const MaxUint16 = ^uint16(0)
|
||||
|
||||
// MinUint16 - minimum value which can be held in an uint8
|
||||
const MinUint16 = 0
|
||||
|
||||
const (
|
||||
letterIdxBits = 6 // 6 bits to represent a letter index
|
||||
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||
@@ -85,6 +98,41 @@ func CountDigits(i int64) (count int) {
|
||||
return count
|
||||
}
|
||||
|
||||
func CheckDataDirectoryPermissions() error {
|
||||
recordingsDirectory := "./data/recordings"
|
||||
configDirectory := "./data/config"
|
||||
snapshotsDirectory := "./data/snapshots"
|
||||
|
||||
err := CheckDirectoryPermissions(recordingsDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = CheckDirectoryPermissions(configDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = CheckDirectoryPermissions(snapshotsDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckDirectoryPermissions(directory string) error {
|
||||
file := directory + "/.test"
|
||||
f, err := os.Create(file)
|
||||
defer f.Close()
|
||||
if err == nil {
|
||||
err := os.Remove(file)
|
||||
if err == nil {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("Problem deleting a file: " + err.Error())
|
||||
}
|
||||
}
|
||||
return errors.New("Problem creating a file: " + err.Error())
|
||||
}
|
||||
|
||||
func ReadDirectory(directory string) ([]os.FileInfo, error) {
|
||||
ff, err := ioutil.ReadDir(directory)
|
||||
if err != nil {
|
||||
@@ -186,6 +234,13 @@ func NumberOfFilesInDirectory(path string) int {
|
||||
return len(files)
|
||||
}
|
||||
|
||||
// NumberOfMP4sInDirectory returns the count of all files with mp4 extension in current directory
|
||||
func NumberOfMP4sInDirectory(path string) int {
|
||||
pattern := filepath.Join(path, "*.mp4")
|
||||
files, _ := filepath.Glob(pattern)
|
||||
return len(files)
|
||||
}
|
||||
|
||||
func RandStringBytesRmndr(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
@@ -197,7 +252,8 @@ func RandStringBytesRmndr(n int) string {
|
||||
func CreateFragmentedMP4(fullName string, fragmentedDuration int64) {
|
||||
path, _ := os.Getwd()
|
||||
duration := fragmentedDuration * 1000
|
||||
cmd := exec.Command("mp4fragment", "--fragment-duration", strconv.FormatInt(duration, 10), fullName, fullName+"f.mp4")
|
||||
// This timescale is crucial, as it should be the same as the one defined in JOY4.
|
||||
cmd := exec.Command("mp4fragment", "--timescale", "10000", "--fragment-duration", strconv.FormatInt(duration, 10), fullName, fullName+"f.mp4")
|
||||
cmd.Dir = path
|
||||
log.Log.Info(cmd.String())
|
||||
var out bytes.Buffer
|
||||
@@ -215,3 +271,108 @@ func CreateFragmentedMP4(fullName string, fragmentedDuration int64) {
|
||||
os.Remove(fullName)
|
||||
os.Rename(fullName+"f.mp4", fullName)
|
||||
}
|
||||
|
||||
/*func FloatToString(input_num float64) string {
|
||||
// to convert a float number to a string
|
||||
return strconv.FormatFloat(input_num, 'f', 6, 64)
|
||||
}
|
||||
|
||||
func ParseFMP4(file *bytes.Reader) (error, uint64, uint64, []*mp4.MediaSegment, uint64) {
|
||||
var ftypSize, moovSize uint64
|
||||
var segments []*mp4.MediaSegment
|
||||
var duration uint64
|
||||
parsedMp4, err := mp4.DecodeFile(file, mp4.WithDecodeMode(mp4.DecModeLazyMdat))
|
||||
if parsedMp4 != nil && parsedMp4.Init != nil && err == nil {
|
||||
ftypSize = parsedMp4.Init.Ftyp.Size()
|
||||
moovSize = parsedMp4.Init.Moov.Size()
|
||||
duration = parsedMp4.Moov.Trak.Tkhd.Duration
|
||||
segments = parsedMp4.Segments
|
||||
}
|
||||
return err, ftypSize, moovSize, segments, duration
|
||||
}
|
||||
|
||||
func ParseFMP4Detail(fullName string) {
|
||||
|
||||
// open fullName
|
||||
file, err := os.Open(fullName)
|
||||
if err != nil {
|
||||
log.Log.Error("Error opening file: " + err.Error())
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fileBytes, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
log.Log.Error("Error reading file: " + err.Error())
|
||||
}
|
||||
fileReader := bytes.NewReader(fileBytes)
|
||||
|
||||
err, ftypSize, moovSize, segments, dur := ParseFMP4(fileReader)
|
||||
fmt.Println("========== Fragmented details =================")
|
||||
fmt.Println(dur)
|
||||
fmt.Println(ftypSize)
|
||||
fmt.Println(moovSize)
|
||||
fmt.Println(len(segments))
|
||||
|
||||
// Calculate duration of segments
|
||||
var totalDuration uint64
|
||||
for _, segment := range segments {
|
||||
fragments := segment.Fragments
|
||||
|
||||
for _, fragment := range fragments {
|
||||
var sampleDuration uint32
|
||||
samples := fragment.Moof.Traf.Trun.Samples
|
||||
for _, sample := range samples {
|
||||
sampleDuration += sample.Dur
|
||||
}
|
||||
fmt.Println(sampleDuration)
|
||||
totalDuration += uint64(sampleDuration)
|
||||
}
|
||||
}
|
||||
|
||||
misingDuration := dur - totalDuration/100
|
||||
fmt.Println(misingDuration)
|
||||
fmt.Println(totalDuration/100 + misingDuration)
|
||||
}
|
||||
|
||||
func CreateFragmentByteRanges(log log.Logging, fileName string, ftypSize uint64, moovSize uint64, segments []*mp4.MediaSegment) (string, []models.BytesRangeOnTime) {
|
||||
size := strconv.FormatInt(int64(ftypSize+moovSize), 10)
|
||||
|
||||
var fileFragments strings.Builder
|
||||
fileFragments.WriteString("#EXT-X-MAP:URI=\"" + fileName + "\",BYTERANGE=\"" + size + "@0\"\n")
|
||||
|
||||
var bytesRangeOnTime []models.BytesRangeOnTime
|
||||
var currentTime uint32
|
||||
for _, segment := range segments {
|
||||
fragments := segment.Fragments
|
||||
for _, fragment := range fragments {
|
||||
var sampleDuration uint32
|
||||
samples := fragment.Moof.Traf.Trun.Samples
|
||||
for _, sample := range samples {
|
||||
sampleDuration += sample.Dur
|
||||
}
|
||||
currentTime = currentTime + sampleDuration
|
||||
duration := FloatToString(float64(sampleDuration / 100000))
|
||||
|
||||
startPos := fragment.Moof.StartPos
|
||||
start := strconv.FormatInt(int64(startPos), 10)
|
||||
|
||||
totalSize := fragment.Mdat.Size() + fragment.Moof.Size()
|
||||
size := strconv.FormatInt(int64(totalSize), 10)
|
||||
|
||||
fileFragments.WriteString("#EXTINF:" + duration + ",\n") // @TODO calculate the duration
|
||||
fileFragments.WriteString("#EXT-X-BYTERANGE:" + size + "@" + start + "\n")
|
||||
fileFragments.WriteString(fileName + "\n")
|
||||
|
||||
byteRange := models.BytesRangeOnTime{
|
||||
Duration: duration,
|
||||
Time: FloatToString(float64(currentTime) / 100000),
|
||||
Range: size + "@" + start,
|
||||
}
|
||||
bytesRangeOnTime = append(bytesRangeOnTime, byteRange)
|
||||
}
|
||||
}
|
||||
|
||||
bytesRanges := fileFragments.String()
|
||||
log.Info("Fragments calculate from " + fileName + ": " + bytesRanges)
|
||||
return bytesRanges, bytesRangeOnTime
|
||||
}*/
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"redux-thunk": "^2.4.1",
|
||||
"rxjs": "^7.5.6",
|
||||
"typeface-roboto": "^1.1.13",
|
||||
"uuid": "^8.3.2",
|
||||
"uuidv4": "2.0.0",
|
||||
"vott-ct": "^2.3.3",
|
||||
"web-vitals": "^0.2.4"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(function(window) {
|
||||
window["env"] = window["env"] || {};
|
||||
(function (window) {
|
||||
window['env'] = window['env'] || {};
|
||||
// Environment variables
|
||||
window["env"]["apiUrl"] = "http://api.factory.kerberos.io";
|
||||
window['env']['apiUrl'] = 'http://api.factory.kerberos.io';
|
||||
})(this);
|
||||
|
||||
@@ -1,15 +1,204 @@
|
||||
{
|
||||
"breadcrumb": {
|
||||
"watch_recordings": "Aufnahmen ansehen",
|
||||
"configure": "Konfigurieren"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Speichern"
|
||||
},
|
||||
"navigation": {
|
||||
"profile": "",
|
||||
"admin": "",
|
||||
"management": "",
|
||||
"dashboard": "",
|
||||
"recordings": "",
|
||||
"settings": "",
|
||||
"help_support": "",
|
||||
"swagger": "",
|
||||
"documentation": "",
|
||||
"ui_library": "",
|
||||
"layout": ""
|
||||
"profile": "Profil",
|
||||
"admin": "admin",
|
||||
"management": "Management",
|
||||
"dashboard": "Übersicht",
|
||||
"recordings": "Aufnahmen",
|
||||
"settings": "Einstellungen",
|
||||
"help_support": "Hilfe und Unterstützung",
|
||||
"swagger": "Swagger API",
|
||||
"documentation": "Dokumentation",
|
||||
"ui_library": "UI Bibliothek",
|
||||
"layout": "Sprache & Layout",
|
||||
"choose_language": "Sprache wählen"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Übersicht",
|
||||
"heading": "Übersicht der Videoüberwachung",
|
||||
"number_of_days": "Anzahl der Tage",
|
||||
"total_recordings": "Aufnahmen insgesamt",
|
||||
"connected": "Verbunden",
|
||||
"not_connected": "Nicht verbunden",
|
||||
"offline_mode": "Offline Modus",
|
||||
"latest_events": "Letzte Ereignisse",
|
||||
"configure_connection": "Verbindungseinstellungen",
|
||||
"no_events": "Keine Ereignisse",
|
||||
"no_events_description": "Es konnten keine Ereignisse gefunden werden. Stelle sicher das der Kerberos Agent richtig konfiguriert ist.",
|
||||
"motion_detected": "Bewegung erkannt",
|
||||
"live_view": "Live Übertragung",
|
||||
"loading_live_view": "Live Übertragung laden",
|
||||
"loading_live_view_description": "Die Live Übertragung wird geladen. Sollte noch keine Kamera konfiguriert sein, kannst du dies in den Einstellungen machen.",
|
||||
"time": "Zeit",
|
||||
"description": "Beschreibung",
|
||||
"name": "Name"
|
||||
},
|
||||
"recordings": {
|
||||
"title": "Aufnahmen",
|
||||
"heading": "Alle Aufnahmen an einem zentralen Ort",
|
||||
"search_media": "Aufnahmen durchsuchen"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Einstellungen",
|
||||
"heading": "Übersicht der Kamera und Agent Einstellungen",
|
||||
"submenu": {
|
||||
"all": "Alle Einstellungen",
|
||||
"overview": "Übersicht",
|
||||
"camera": "Kamera",
|
||||
"recording": "Aufnahme",
|
||||
"streaming": "Stream",
|
||||
"conditions": "Kriterien",
|
||||
"persistence": "Persistenz"
|
||||
},
|
||||
"info": {
|
||||
"kerberos_hub_demo": "Um Kerberos Hub in Aktion zu sehen, schaue dir gerne unsere Demo an!",
|
||||
"configuration_updated_success": "Die Einstellungen wurden erfolgreich gespeichert.",
|
||||
"configuration_updated_error": "Beim speichern der Einstellungen ist ein Fehler aufgetreten!",
|
||||
"verify_hub": "Überprüfen der Kerberos Hub Einstellungen.",
|
||||
"verify_hub_success": "Die Kerberos Hub Einstellung wurden erfolgreich überprüft",
|
||||
"verify_hub_error": "Beim überprüfen der Kerberos Hub Einstellungen ist ein Fehler aufgetreten",
|
||||
"verify_persistence": "Überprüfen der Speichereinstellungen.",
|
||||
"verify_persistence_success": "Die Speichereinstellungen wurden erfolgreich überprüft.",
|
||||
"verify_persistence_error": "Beim überprüfen der Speichereinstellungen ist ein Fehler aufgetreten",
|
||||
"verify_camera": "Überprüfen der Kameraeinstellungen.",
|
||||
"verify_camera_success": "Die Kameraeinstellungen wurden erfolgreich überprüft.",
|
||||
"verify_camera_error": "Beim überprüfen der Kameraeinstellungen ist ein Fehler aufgetreten"
|
||||
},
|
||||
"overview": {
|
||||
"general": "Allgemein",
|
||||
"description_general": "Allgemeine Einstellungen für den Kerberos Agent",
|
||||
"key": "Schlüssel",
|
||||
"camera_name": "Kamera Name",
|
||||
"timezone": "Zeitzone",
|
||||
"select_timezone": "Zeitzone auswählen",
|
||||
"advanced_configuration": "Erweiterte Konfiguration",
|
||||
"description_advanced_configuration": "Erweiterte Einstellungen um Funktionen des Kerberos Agent zu aktivieren oder deaktivieren",
|
||||
"offline_mode": "Offline Modus",
|
||||
"description_offline_mode": "Ausgehende Verbindungen deaktivieren"
|
||||
},
|
||||
"camera": {
|
||||
"camera": "Kamera",
|
||||
"description_camera": "Diese Einstellungen sind notwendig um eine Verbindung mit der Kamera herzustellen",
|
||||
"only_h264": "Aktuell werden nur H264 RTSP kompatible Kameras unterstützt",
|
||||
"rtsp_url": "RTSP URL",
|
||||
"rtsp_h264": "H264 RTSP URL der Kamera",
|
||||
"sub_rtsp_url": "RTSP url für die Live Übertragung.",
|
||||
"sub_rtsp_h264": "Ergänzende URL der Kamera mit geringerer Auflösung für die Live Übertragung.",
|
||||
"onvif": "ONVIF",
|
||||
"description_onvif": "Zugangsdaten der ONVIF Funktionen der Kamera. Diese werden zur Steuerung der Kamera, falls vorhanden, verwendet.",
|
||||
"onvif_xaddr": "ONVIF xaddr",
|
||||
"onvif_username": "ONVIF Benutzername",
|
||||
"onvif_password": "ONVIF Passwort",
|
||||
"verify_connection": "Verbindung prüfen",
|
||||
"verify_sub_connection": "Live Verbindung prüfen"
|
||||
},
|
||||
"recording": {
|
||||
"recording": "Aufnahme",
|
||||
"description_recording": "Einstellungen zum Aufnahmeverhalten. Aufnahmen können fortlaufend oder anhand der Bewegungserkennung geamcht werden.",
|
||||
"continuous_recording": "Fortlaufende Aufnahme",
|
||||
"description_continuous_recording": "24/7 oder Bewegungserkennung",
|
||||
"max_duration": "Maximale Dauer der Aufnahme (Sekunden)",
|
||||
"description_max_duration": "Maximale dauer der Aufnahme in Sekunden",
|
||||
"pre_recording": "Vorlauf der Aufnahme (Sekunden)",
|
||||
"description_pre_recording": "Sekunden vor der Erkennung einer Bewegung",
|
||||
"post_recording": "Nachlauf der Aufnahme (Sekunden)",
|
||||
"description_post_recording": "Sekunden nach der Erkennung einer Bewegung",
|
||||
"threshold": "Schwellwert der Aufnahme (Pixel)",
|
||||
"description_threshold": "Anzahl der geänderten Pixel die notwendig sind um die Aufnahem zu starten",
|
||||
"autoclean": "Automatisches aufräumen",
|
||||
"description_autoclean": "Der Kerberos Agent kann, wenn gewünscht, den Speicher automatisch aufräumen wenn ein vorgegebener Schwellwert erreicht ist. Es werden immer zuerst die ältesten Aufnahemn entfernt.",
|
||||
"autoclean_enable": "Automatisches Aufräumen aktivieren",
|
||||
"autoclean_description_enable": "Entferne die älteste Aufnahme wenn der Schwellwert erreicht wurde.",
|
||||
"autoclean_max_directory_size": "Maximale Größe des Verzeichnisses (MB)",
|
||||
"autoclean_description_max_directory_size": "Maximale größe der gespeichterten Aufnahmen im Verzeichnis in MB.",
|
||||
"fragmentedrecordings": "Fragmentierte Aufnahmen",
|
||||
"description_fragmentedrecordings": "Wenn die Aufnahmen fragmentiert werden können diese auch für HLS Übertragungen genutzt werden. Der Inhalt des MP4 Kontainers sieht dann anders aus.",
|
||||
"fragmentedrecordings_enable": "Fragmentierung aktivieren",
|
||||
"fragmentedrecordings_description_enable": "Die Fragmentierung muss aktiviert sein, wenn HLS verwendet werden soll.",
|
||||
"fragmentedrecordings_duration": "Dauer eines Fragments",
|
||||
"fragmentedrecordings_description_duration": "Dauer eines Fragments in Sekunden"
|
||||
},
|
||||
"streaming": {
|
||||
"stun_turn": "STUN/TURN für WebRTC",
|
||||
"description_stun_turn": "For full-resolution livestreaming we use the concept of WebRTC. One of the key capabilities is the ICE-candidate feature, which allows NAT traversal using the concepts of STUN/TURN.",
|
||||
"stun_server": "STUN Server",
|
||||
"turn_server": "TURN Server",
|
||||
"turn_username": "Benutzername",
|
||||
"turn_password": "Passwort",
|
||||
"stun_turn_forward": "Weiterleiten und transkodieren",
|
||||
"stun_turn_description_forward": "Optiemierungen und Verbesserungen der TURN/STUN Kommunikation.",
|
||||
"stun_turn_webrtc": "Weiterleiten an WebRTC Schnittstelle",
|
||||
"stun_turn_description_webrtc": "Weiterleiten der H264 Übertragung über MQTT",
|
||||
"stun_turn_transcode": "Übertragung transkodieren",
|
||||
"stun_turn_description_transcode": "Übertragung in geringerer Auflösung",
|
||||
"stun_turn_downscale": "Reduzierung der Auflösung in % zur original Auflösung",
|
||||
"mqtt": "MQTT",
|
||||
"description_mqtt": "MQTT wird verwendet um Video und ONVIF von",
|
||||
"description2_mqtt": "zum Kerberos Agent zu realisieren.",
|
||||
"mqtt_brokeruri": "MQTT Uri",
|
||||
"mqtt_username": "Benutzername",
|
||||
"mqtt_password": "Passwort"
|
||||
},
|
||||
"conditions": {
|
||||
"timeofinterest": "Zeitraum",
|
||||
"description_timeofinterest": "Aufnahmen nur in einem vorgegebenen Zeitraum erfassen. Hierzu sollte die Zeitzone in den Einstellungen korrekt hinterlegt sein.",
|
||||
"timeofinterest_enabled": "Aktivieren",
|
||||
"timeofinterest_description_enabled": "Wenn aktiviert, kann der Zeitraum der Aufnahmen definiert werden",
|
||||
"sunday": "Sonntag",
|
||||
"monday": "Montag",
|
||||
"tuesday": "Dienstag",
|
||||
"wednesday": "Mittwoch",
|
||||
"thursday": "Donnerstag",
|
||||
"friday": "Freitag",
|
||||
"saturday": "Samstag",
|
||||
"externalcondition": "Externe Aufnahmekriterien",
|
||||
"description_externalcondition": "Anhand einer externer Kriterien kann die Aufnahme aktiviert oder deaktiviert werden.",
|
||||
"regionofinterest": "Aufnahmebereich",
|
||||
"description_regionofinterest": "Durch das definieren von Aufnahmebereichen kann gesteuert werden, in welchem Bereich Bewegungen erfasst werden sollen."
|
||||
},
|
||||
"persistence": {
|
||||
"kerberoshub": "Kerberos Hub",
|
||||
"description_kerberoshub": "Kerberos Agents können Zustandsinformationen an eine Zentrale",
|
||||
"description2_kerberoshub": "installation senden. Zustands- und relevante Systeminformationen werden zentral abgelegt um einen Überblick über die Videoüberwachung zu erhalten.",
|
||||
"persistence": "Persistenz",
|
||||
"saasoffering": "Kerberos Hub (SAAS Angebot)",
|
||||
"description_persistence": "Die möglichkeit zur Speicherung der Daten an einem Zentralen Ort ist der Beginn einer effektiven Videoüberwachung. Es kann zwischen",
|
||||
"description2_persistence": ", oder einem Drittanbieter gewählt werden.",
|
||||
"select_persistence": "Speicherort auswählen",
|
||||
"kerberoshub_proxyurl": "Kerberos Hub Proxy URL",
|
||||
"kerberoshub_description_proxyurl": "Der Proxy Endpunkt zum hochladen der Aufnahmen.",
|
||||
"kerberoshub_apiurl": "Kerberos Hub API URL",
|
||||
"kerberoshub_description_apiurl": "Der API Endpunkt zum hochladen der Aufnahmen.",
|
||||
"kerberoshub_publickey": "Öffentlicher Schlüssel",
|
||||
"kerberoshub_description_publickey": "Der öffentliche Schlüssel des Kerberos Hub Accounts.",
|
||||
"kerberoshub_privatekey": "Privater Schlüssel",
|
||||
"kerberoshub_description_privatekey": "Der private Schlüssel des Kerberos Hub Accounts.",
|
||||
"kerberoshub_site": "Standort",
|
||||
"kerberoshub_description_site": "Die Standort ID zu der die Daten im Kerberos Hub gehören.",
|
||||
"kerberoshub_region": "Region",
|
||||
"kerberoshub_description_region": "Die Region in der die Aufnahmen gespeichert werden sollen.",
|
||||
"kerberoshub_bucket": "Bucket",
|
||||
"kerberoshub_description_bucket": "Der Bucket in dem die Aufnahmen gespeichert werden.",
|
||||
"kerberoshub_username": "Benutzername/Verzeichnis",
|
||||
"kerberoshub_description_username": "Der Benutzername des Kerberos Hub Accounts.",
|
||||
"kerberosvault_apiurl": "Kerberos Vault API URL",
|
||||
"kerberosvault_description_apiurl": "Die Kerberos Vault API URL.",
|
||||
"kerberosvault_provider": "Anbieter",
|
||||
"kerberosvault_description_provider": "Der Anbieter zu dem die Aufnahmen gesendet werden.",
|
||||
"kerberosvault_directory": "Verzeichnis",
|
||||
"kerberosvault_description_directory": "Das Uneterverzeichnis in dem die Aufnahmen gespeichert werden sollen.",
|
||||
"kerberosvault_accesskey": "Zugriffsschlüssel",
|
||||
"kerberosvault_description_accesskey": "Der Zugriffsschlüssel des Kerberos Vault Accounts..",
|
||||
"kerberosvault_secretkey": "Geheimer Schlüssel",
|
||||
"kerberosvault_description_secretkey": "Der geheime Schlüssel des Kerberos Vault Accounts.",
|
||||
"verify_connection": "Verbindung prüfen"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
"watch_recordings": "Watch recordings",
|
||||
"configure": "Configure"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Save"
|
||||
},
|
||||
"navigation": {
|
||||
"profile": "Profile",
|
||||
"admin": "admin",
|
||||
"management": "Management",
|
||||
"dashboard": "Dashboard",
|
||||
"recordings": "Recordings",
|
||||
"recordings": "Recordings",
|
||||
"settings": "Settings",
|
||||
"help_support": "Help & Support",
|
||||
"swagger": "Swagger API",
|
||||
@@ -21,6 +24,7 @@
|
||||
"title": "Dashboard",
|
||||
"heading": "Overview of your video surveilance",
|
||||
"number_of_days": "Number of days",
|
||||
"total_recordings": "Total recordings",
|
||||
"connected": "Connected",
|
||||
"not_connected": "Not connected",
|
||||
"offline_mode": "Offline mode",
|
||||
@@ -35,5 +39,166 @@
|
||||
"time": "Time",
|
||||
"description": "Description",
|
||||
"name": "Name"
|
||||
},
|
||||
"recordings": {
|
||||
"title": "Recordings",
|
||||
"heading": "All your recordings in a single place",
|
||||
"search_media": "Search media"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"heading": "Onboard your camera",
|
||||
"submenu": {
|
||||
"all": "All",
|
||||
"overview": "Overview",
|
||||
"camera": "Camera",
|
||||
"recording": "Recording",
|
||||
"streaming": "Streaming",
|
||||
"conditions": "Conditions",
|
||||
"persistence": "Persistence"
|
||||
},
|
||||
"info": {
|
||||
"kerberos_hub_demo": "Have a look at our Kerberos Hub demo environment, to see Kerberos Hub in action!",
|
||||
"configuration_updated_success": "Your configuration have been updated successfully.",
|
||||
"configuration_updated_error": "Something went wrong while saving.",
|
||||
"verify_hub": "Verifying your Kerberos Hub settings.",
|
||||
"verify_hub_success": "Kerberos Hub settings are successfully verified.",
|
||||
"verify_hub_error": "Something went wrong while verifying Kerberos Hub",
|
||||
"verify_persistence": "Verifying your persistence settings.",
|
||||
"verify_persistence_success": "Persistence settings are successfully verified.",
|
||||
"verify_persistence_error": "Something went wrong while verifying the persistence",
|
||||
"verify_camera": "Verifying your camera settings.",
|
||||
"verify_camera_success": "Camera settings are successfully verified.",
|
||||
"verify_camera_error": "Something went wrong while verifying the camera settings"
|
||||
},
|
||||
"overview": {
|
||||
"general": "General",
|
||||
"description_general": "General settings for your Kerberos Agent",
|
||||
"key": "Key",
|
||||
"camera_name": "Camera name",
|
||||
"timezone": "Timezone",
|
||||
"select_timezone": "Select a timezone",
|
||||
"advanced_configuration": "Advanced configuration",
|
||||
"description_advanced_configuration": "Detailed configuration options to enable or disable specific parts of the Kerberos Agent",
|
||||
"offline_mode": "Offline mode",
|
||||
"description_offline_mode": "Disable all outgoing traffic"
|
||||
},
|
||||
"camera": {
|
||||
"camera": "Camera",
|
||||
"description_camera": "Camera settings are required to make a connection to your camera of choice.",
|
||||
"only_h264": "Currently only H264 RTSP streams are supported.",
|
||||
"rtsp_url": "RTSP url",
|
||||
"rtsp_h264": "A H264 RTSP connection to your camera.",
|
||||
"sub_rtsp_url": "Sub RTSP url (used for livestreaming)",
|
||||
"sub_rtsp_h264": "A secondary RTSP connection to the low resolution of your camera.",
|
||||
"onvif": "ONVIF",
|
||||
"description_onvif": "Credentials to communicate with ONVIF capabilities. These are used for PTZ or other capabilities provided by the camera.",
|
||||
"onvif_xaddr": "ONVIF xaddr",
|
||||
"onvif_username": "ONVIF username",
|
||||
"onvif_password": "ONVIF password",
|
||||
"verify_connection": "Verify Connection",
|
||||
"verify_sub_connection": "Verify Sub Connection"
|
||||
},
|
||||
"recording": {
|
||||
"recording": "Recording",
|
||||
"description_recording": "Specify how you would like to make recordings. Having a continuous 24/7 setup or a motion based recording.",
|
||||
"continuous_recording": "Continuous recording",
|
||||
"description_continuous_recording": "Make 24/7 or motion based recordings.",
|
||||
"max_duration": "max video duration (seconds)",
|
||||
"description_max_duration": "The maximum duration of a recording.",
|
||||
"pre_recording": "pre recording (key frames buffered)",
|
||||
"description_pre_recording": "Seconds before an event occurred.",
|
||||
"post_recording": "post recording (seconds)",
|
||||
"description_post_recording": "Seconds after an event occurred.",
|
||||
"threshold": "Recording threshold (pixels)",
|
||||
"description_threshold": "The number of pixels changed to record",
|
||||
"autoclean": "Auto clean",
|
||||
"description_autoclean": "Specify if the Kerberos Agent can cleanup recordings when a specific storage capacity (MB) is reached. This will remove the oldest recordings when the capacity is reached.",
|
||||
"autoclean_enable": "Enable auto clean",
|
||||
"autoclean_description_enable": "Remove oldest recording when capacity reached.",
|
||||
"autoclean_max_directory_size": "Maximum directory size (MB)",
|
||||
"autoclean_description_max_directory_size": "The maximum MB's of recordings stored.",
|
||||
"fragmentedrecordings": "Fragmented recordings",
|
||||
"description_fragmentedrecordings": "When recordings are fragmented they are suitable for an HLS stream. When turned on the MP4 container will look a bit different.",
|
||||
"fragmentedrecordings_enable": "Enable fragmentation",
|
||||
"fragmentedrecordings_description_enable": "Fragmented recordings are required for HLS.",
|
||||
"fragmentedrecordings_duration": "fragment duration",
|
||||
"fragmentedrecordings_description_duration": "Duration of a single fragment."
|
||||
},
|
||||
"streaming": {
|
||||
"stun_turn": "STUN/TURN for WebRTC",
|
||||
"description_stun_turn": "For full-resolution livestreaming we use the concept of WebRTC. One of the key capabilities is the ICE-candidate feature, which allows NAT traversal using the concepts of STUN/TURN.",
|
||||
"stun_server": "STUN server",
|
||||
"turn_server": "TURN server",
|
||||
"turn_username": "Username",
|
||||
"turn_password": "Password",
|
||||
"stun_turn_forward": "Forwarding and transcoding",
|
||||
"stun_turn_description_forward": "Optimisations and enhancements for TURN/STUN communication.",
|
||||
"stun_turn_webrtc": "Forwarding to WebRTC broker",
|
||||
"stun_turn_description_webrtc": "Forward h264 stream through MQTT",
|
||||
"stun_turn_transcode": "Transcode stream",
|
||||
"stun_turn_description_transcode": "Convert stream to a lower resolution",
|
||||
"stun_turn_downscale": "Downscale resolution (in % or original resolution)",
|
||||
"mqtt": "MQTT",
|
||||
"description_mqtt": "A MQTT broker is used to communicate from",
|
||||
"description2_mqtt": "to the Kerberos Agent, to achieve for example livestreaming or ONVIF (PTZ) capabilities.",
|
||||
"mqtt_brokeruri": "Broker Uri",
|
||||
"mqtt_username": "Username",
|
||||
"mqtt_password": "Password"
|
||||
},
|
||||
"conditions": {
|
||||
"timeofinterest": "Time Of Interest",
|
||||
"description_timeofinterest": "Only make recordings between specific time intervals (based on timezone).",
|
||||
"timeofinterest_enabled": "Enabled",
|
||||
"timeofinterest_description_enabled": "If enabled you can specify time windows",
|
||||
"sunday": "Sunday",
|
||||
"monday": "Monday",
|
||||
"tuesday": "Tuesday",
|
||||
"wednesday": "Wednesday",
|
||||
"thursday": "Thursday",
|
||||
"friday": "Friday",
|
||||
"saturday": "Saturday",
|
||||
"externalcondition": "External Condition",
|
||||
"description_externalcondition": "Depending on an external webservice recording can be enabled or disabled.",
|
||||
"regionofinterest": "Region Of Interest",
|
||||
"description_regionofinterest": "By defining one or more regions, motion will be tracked only in the regions you have defined."
|
||||
},
|
||||
"persistence": {
|
||||
"kerberoshub": "Kerberos Hub",
|
||||
"description_kerberoshub": "Kerberos Agents can send heartbeats to a central",
|
||||
"description2_kerberoshub": "installation. Heartbeats and other relevant information are synced to Kerberos Hub to show realtime information about your video landscape.",
|
||||
"persistence": "Persistence",
|
||||
"saasoffering": "Kerberos Hub (SAAS offering)",
|
||||
"description_persistence": "Having the ability to store your recordings is the beginning of everything. You can choose between our",
|
||||
"description2_persistence": ", or a 3rd party provider",
|
||||
"select_persistence": "Select a persistence",
|
||||
"kerberoshub_proxyurl": "Kerberos Hub Proxy URL",
|
||||
"kerberoshub_description_proxyurl": "The Proxy endpoint for uploading your recordings.",
|
||||
"kerberoshub_apiurl": "Kerberos Hub API URL",
|
||||
"kerberoshub_description_apiurl": "The API endpoint for uploading your recordings.",
|
||||
"kerberoshub_publickey": "Public key",
|
||||
"kerberoshub_description_publickey": "The public key granted to your Kerberos Hub account.",
|
||||
"kerberoshub_privatekey": "Private key",
|
||||
"kerberoshub_description_privatekey": "The private key granted to your Kerberos Hub account.",
|
||||
"kerberoshub_site": "Site",
|
||||
"kerberoshub_description_site": "The site ID the Kerberos Agents are belonging to in Kerberos Hub.",
|
||||
"kerberoshub_region": "Region",
|
||||
"kerberoshub_description_region": "The region we are storing our recordings in.",
|
||||
"kerberoshub_bucket": "Bucket",
|
||||
"kerberoshub_description_bucket": "The bucket we are storing our recordings in.",
|
||||
"kerberoshub_username": "Username/Directory",
|
||||
"kerberoshub_description_username": "The username of your Kerberos Hub account.",
|
||||
"kerberosvault_apiurl": "Kerberos Vault API URL",
|
||||
"kerberosvault_description_apiurl": "The Kerberos Vault API",
|
||||
"kerberosvault_provider": "Provider",
|
||||
"kerberosvault_description_provider": "The provider to which your recordings will be send.",
|
||||
"kerberosvault_directory": "Directory",
|
||||
"kerberosvault_description_directory": "Sub directory the recordings will be stored in your provider.",
|
||||
"kerberosvault_accesskey": "Access key",
|
||||
"kerberosvault_description_accesskey": "The access key of your Kerberos Vault account.",
|
||||
"kerberosvault_secretkey": "Secret key",
|
||||
"kerberosvault_description_secretkey": "The secret key of your Kerberos Vault account.",
|
||||
"verify_connection": "Verify Connection"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
204
ui/public/locales/es/translation.json
Normal file
204
ui/public/locales/es/translation.json
Normal file
@@ -0,0 +1,204 @@
|
||||
{
|
||||
"breadcrumb": {
|
||||
"watch_recordings": "Watch recordings",
|
||||
"configure": "Configure"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Save"
|
||||
},
|
||||
"navigation": {
|
||||
"profile": "Perfil",
|
||||
"admin": "admin",
|
||||
"management": "Administración",
|
||||
"dashboard": "Tablero",
|
||||
"recordings": "Grabaciones",
|
||||
"settings": "Ajustes",
|
||||
"help_support": "Servicio de asistencia",
|
||||
"swagger": "Swagger API",
|
||||
"documentation": "Documentación",
|
||||
"ui_library": "UI Biblioteca",
|
||||
"layout": "Idioma y diseño",
|
||||
"choose_language": "Elige lengua"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"heading": "Overview of your video surveilance",
|
||||
"number_of_days": "Number of days",
|
||||
"total_recordings": "Total recordings",
|
||||
"connected": "Connected",
|
||||
"not_connected": "Not connected",
|
||||
"offline_mode": "Offline mode",
|
||||
"latest_events": "Latest events",
|
||||
"configure_connection": "Configure connection",
|
||||
"no_events": "No events",
|
||||
"no_events_description": "No recordings where found, make sure your Kerberos Agent is properly configured.",
|
||||
"motion_detected": "Motion was detected",
|
||||
"live_view": "Live view",
|
||||
"loading_live_view": "Loading live view",
|
||||
"loading_live_view_description": "Hold on we are loading your live view here. If you didn't configure your camera connection, update it on the settings pages.",
|
||||
"time": "Time",
|
||||
"description": "Description",
|
||||
"name": "Name"
|
||||
},
|
||||
"recordings": {
|
||||
"title": "Recordings",
|
||||
"heading": "All your recordings in a single place",
|
||||
"search_media": "Search media"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"heading": "Onboard your camera",
|
||||
"submenu": {
|
||||
"all": "All",
|
||||
"overview": "Overview",
|
||||
"camera": "Camera",
|
||||
"recording": "Recording",
|
||||
"streaming": "Streaming",
|
||||
"conditions": "Conditions",
|
||||
"persistence": "Persistence"
|
||||
},
|
||||
"info": {
|
||||
"kerberos_hub_demo": "Have a look at our Kerberos Hub demo environment, to see Kerberos Hub in action!",
|
||||
"configuration_updated_success": "Your configuration have been updated successfully.",
|
||||
"configuration_updated_error": "Something went wrong while saving.",
|
||||
"verify_hub": "Verifying your Kerberos Hub settings.",
|
||||
"verify_hub_success": "Kerberos Hub settings are successfully verified.",
|
||||
"verify_hub_error": "Something went wrong while verifying Kerberos Hub",
|
||||
"verify_persistence": "Verifying your persistence settings.",
|
||||
"verify_persistence_success": "Persistence settings are successfully verified.",
|
||||
"verify_persistence_error": "Something went wrong while verifying the persistence",
|
||||
"verify_camera": "Verifying your camera settings.",
|
||||
"verify_camera_success": "Camera settings are successfully verified.",
|
||||
"verify_camera_error": "Something went wrong while verifying the camera settings"
|
||||
},
|
||||
"overview": {
|
||||
"general": "General",
|
||||
"description_general": "General settings for your Kerberos Agent",
|
||||
"key": "Key",
|
||||
"camera_name": "Camera name",
|
||||
"timezone": "Timezone",
|
||||
"select_timezone": "Select a timezone",
|
||||
"advanced_configuration": "Advanced configuration",
|
||||
"description_advanced_configuration": "Detailed configuration options to enable or disable specific parts of the Kerberos Agent",
|
||||
"offline_mode": "Offline mode",
|
||||
"description_offline_mode": "Disable all outgoing traffic"
|
||||
},
|
||||
"camera": {
|
||||
"camera": "Camera",
|
||||
"description_camera": "Camera settings are required to make a connection to your camera of choice.",
|
||||
"only_h264": "Currently only H264 RTSP streams are supported.",
|
||||
"rtsp_url": "RTSP url",
|
||||
"rtsp_h264": "A H264 RTSP connection to your camera.",
|
||||
"sub_rtsp_url": "Sub RTSP url (used for livestreaming)",
|
||||
"sub_rtsp_h264": "A secondary RTSP connection to the low resolution of your camera.",
|
||||
"onvif": "ONVIF",
|
||||
"description_onvif": "Credentials to communicate with ONVIF capabilities. These are used for PTZ or other capabilities provided by the camera.",
|
||||
"onvif_xaddr": "ONVIF xaddr",
|
||||
"onvif_username": "ONVIF username",
|
||||
"onvif_password": "ONVIF password",
|
||||
"verify_connection": "Verify Connection",
|
||||
"verify_sub_connection": "Verify Sub Connection"
|
||||
},
|
||||
"recording": {
|
||||
"recording": "Recording",
|
||||
"description_recording": "Specify how you would like to make recordings. Having a continuous 24/7 setup or a motion based recording.",
|
||||
"continuous_recording": "Continuous recording",
|
||||
"description_continuous_recording": "Make 24/7 or motion based recordings.",
|
||||
"max_duration": "max video duration (seconds)",
|
||||
"description_max_duration": "The maximum duration of a recording.",
|
||||
"pre_recording": "pre recording (key frames buffered)",
|
||||
"description_pre_recording": "Seconds before an event occurred.",
|
||||
"post_recording": "post recording (seconds)",
|
||||
"description_post_recording": "Seconds after an event occurred.",
|
||||
"threshold": "Recording threshold (pixels)",
|
||||
"description_threshold": "The number of pixels changed to record",
|
||||
"autoclean": "Auto clean",
|
||||
"description_autoclean": "Specify if the Kerberos Agent can cleanup recordings when a specific storage capacity (MB) is reached. This will remove the oldest recordings when the capacity is reached.",
|
||||
"autoclean_enable": "Enable auto clean",
|
||||
"autoclean_description_enable": "Remove oldest recording when capacity reached.",
|
||||
"autoclean_max_directory_size": "Maximum directory size (MB)",
|
||||
"autoclean_description_max_directory_size": "The maximum MB's of recordings stored.",
|
||||
"fragmentedrecordings": "Fragmented recordings",
|
||||
"description_fragmentedrecordings": "When recordings are fragmented they are suitable for an HLS stream. When turned on the MP4 container will look a bit different.",
|
||||
"fragmentedrecordings_enable": "Enable fragmentation",
|
||||
"fragmentedrecordings_description_enable": "Fragmented recordings are required for HLS.",
|
||||
"fragmentedrecordings_duration": "fragment duration",
|
||||
"fragmentedrecordings_description_duration": "Duration of a single fragment."
|
||||
},
|
||||
"streaming": {
|
||||
"stun_turn": "STUN/TURN for WebRTC",
|
||||
"description_stun_turn": "For full-resolution livestreaming we use the concept of WebRTC. One of the key capabilities is the ICE-candidate feature, which allows NAT traversal using the concepts of STUN/TURN.",
|
||||
"stun_server": "STUN server",
|
||||
"turn_server": "TURN server",
|
||||
"turn_username": "Username",
|
||||
"turn_password": "Password",
|
||||
"stun_turn_forward": "Forwarding and transcoding",
|
||||
"stun_turn_description_forward": "Optimisations and enhancements for TURN/STUN communication.",
|
||||
"stun_turn_webrtc": "Forwarding to WebRTC broker",
|
||||
"stun_turn_description_webrtc": "Forward h264 stream through MQTT",
|
||||
"stun_turn_transcode": "Transcode stream",
|
||||
"stun_turn_description_transcode": "Convert stream to a lower resolution",
|
||||
"stun_turn_downscale": "Downscale resolution (in % or original resolution)",
|
||||
"mqtt": "MQTT",
|
||||
"description_mqtt": "A MQTT broker is used to communicate from",
|
||||
"description2_mqtt": "to the Kerberos Agent, to achieve for example livestreaming or ONVIF (PTZ) capabilities.",
|
||||
"mqtt_brokeruri": "Broker Uri",
|
||||
"mqtt_username": "Username",
|
||||
"mqtt_password": "Password"
|
||||
},
|
||||
"conditions": {
|
||||
"timeofinterest": "Time Of Interest",
|
||||
"description_timeofinterest": "Only make recordings between specific time intervals (based on timezone).",
|
||||
"timeofinterest_enabled": "Enabled",
|
||||
"timeofinterest_description_enabled": "If enabled you can specify time windows",
|
||||
"sunday": "Sunday",
|
||||
"monday": "Monday",
|
||||
"tuesday": "Tuesday",
|
||||
"wednesday": "Wednesday",
|
||||
"thursday": "Thursday",
|
||||
"friday": "Friday",
|
||||
"saturday": "Saturday",
|
||||
"externalcondition": "External Condition",
|
||||
"description_externalcondition": "Depending on an external webservice recording can be enabled or disabled.",
|
||||
"regionofinterest": "Region Of Interest",
|
||||
"description_regionofinterest": "By defining one or more regions, motion will be tracked only in the regions you have defined."
|
||||
},
|
||||
"persistence": {
|
||||
"kerberoshub": "Kerberos Hub",
|
||||
"description_kerberoshub": "Kerberos Agents can send heartbeats to a central",
|
||||
"description2_kerberoshub": "installation. Heartbeats and other relevant information are synced to Kerberos Hub to show realtime information about your video landscape.",
|
||||
"persistence": "Persistence",
|
||||
"saasoffering": "Kerberos Hub (SAAS offering)",
|
||||
"description_persistence": "Having the ability to store your recordings is the beginning of everything. You can choose between our",
|
||||
"description2_persistence": ", or a 3rd party provider",
|
||||
"select_persistence": "Select a persistence",
|
||||
"kerberoshub_proxyurl": "Kerberos Hub Proxy URL",
|
||||
"kerberoshub_description_proxyurl": "The Proxy endpoint for uploading your recordings.",
|
||||
"kerberoshub_apiurl": "Kerberos Hub API URL",
|
||||
"kerberoshub_description_apiurl": "The API endpoint for uploading your recordings.",
|
||||
"kerberoshub_publickey": "Public key",
|
||||
"kerberoshub_description_publickey": "The public key granted to your Kerberos Hub account.",
|
||||
"kerberoshub_privatekey": "Private key",
|
||||
"kerberoshub_description_privatekey": "The private key granted to your Kerberos Hub account.",
|
||||
"kerberoshub_site": "Site",
|
||||
"kerberoshub_description_site": "The site ID the Kerberos Agents are belonging to in Kerberos Hub.",
|
||||
"kerberoshub_region": "Region",
|
||||
"kerberoshub_description_region": "The region we are storing our recordings in.",
|
||||
"kerberoshub_bucket": "Bucket",
|
||||
"kerberoshub_description_bucket": "The bucket we are storing our recordings in.",
|
||||
"kerberoshub_username": "Username/Directory",
|
||||
"kerberoshub_description_username": "The username of your Kerberos Hub account.",
|
||||
"kerberosvault_apiurl": "Kerberos Vault API URL",
|
||||
"kerberosvault_description_apiurl": "The Kerberos Vault API",
|
||||
"kerberosvault_provider": "Provider",
|
||||
"kerberosvault_description_provider": "The provider to which your recordings will be send.",
|
||||
"kerberosvault_directory": "Directory",
|
||||
"kerberosvault_description_directory": "Sub directory the recordings will be stored in your provider.",
|
||||
"kerberosvault_accesskey": "Access key",
|
||||
"kerberosvault_description_accesskey": "The access key of your Kerberos Vault account.",
|
||||
"kerberosvault_secretkey": "Secret key",
|
||||
"kerberosvault_description_secretkey": "The secret key of your Kerberos Vault account.",
|
||||
"verify_connection": "Verify Connection"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,204 @@
|
||||
{
|
||||
"breadcrumb": {
|
||||
"watch_recordings": "Visionner les enregistrements",
|
||||
"configure": "Configurer"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Enregistrer"
|
||||
},
|
||||
"navigation": {
|
||||
"profile": "",
|
||||
"admin": "",
|
||||
"management": "",
|
||||
"dashboard": "",
|
||||
"recordings": "",
|
||||
"settings": "",
|
||||
"help_support": "",
|
||||
"swagger": "",
|
||||
"documentation": "",
|
||||
"ui_library": "",
|
||||
"layout": ""
|
||||
"profile": "Profil",
|
||||
"admin": "admin",
|
||||
"management": "Gestion",
|
||||
"dashboard": "Tableau de bord",
|
||||
"recordings": "Enregistrements",
|
||||
"settings": "Paramètres",
|
||||
"help_support": "Aide et Support",
|
||||
"swagger": "API Swagger",
|
||||
"documentation": "Documentation",
|
||||
"ui_library": "Bibliothèque UI",
|
||||
"layout": "Langue et Mise en page",
|
||||
"choose_language": "Choisissez la langue"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Tableau de bord",
|
||||
"heading": "Vue d'ensemble de votre vidéosurveillance",
|
||||
"number_of_days": "Nombre de jours",
|
||||
"total_recordings": "Total d'enregistrements",
|
||||
"connected": "Connexion établie",
|
||||
"not_connected": "Pas de connexion",
|
||||
"offline_mode": "Mode hors-ligne",
|
||||
"latest_events": "Derniers événements",
|
||||
"configure_connection": "Configurer la connexion",
|
||||
"no_events": "Aucun événement",
|
||||
"no_events_description": "Aucun enregistrement n'a été trouvé, assurez-vous de la bonne configuration de votre Agent Kerberos.",
|
||||
"motion_detected": "Un mouvement a été détecté",
|
||||
"live_view": "Visualiser le direct",
|
||||
"loading_live_view": "Chargement de l'affichage en direct",
|
||||
"loading_live_view_description": "Veuillez patienter, nous chargeons ici votre affichage en direct. Si vous n'avez pas configuré la connexion de votre caméra, mettez-la à jour dans les paramètres.",
|
||||
"time": "Temps",
|
||||
"description": "Description",
|
||||
"name": "Nom"
|
||||
},
|
||||
"recordings": {
|
||||
"title": "Enregistrements",
|
||||
"heading": "Tout vos enregistrements au même endroit",
|
||||
"search_media": "Rechercher un média"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Paramètres",
|
||||
"heading": "A bord de votre caméra",
|
||||
"submenu": {
|
||||
"all": "Ensemble",
|
||||
"overview": "Aperçu",
|
||||
"camera": "Caméra",
|
||||
"recording": "Enregistrement",
|
||||
"streaming": "Diffusion continue",
|
||||
"conditions": "Conditions",
|
||||
"persistence": "Persistance"
|
||||
},
|
||||
"info": {
|
||||
"kerberos_hub_demo": "Jeter un œil à notre environnement de démo Kerberos Hub, pour voir Kerberos Hub en action !",
|
||||
"configuration_updated_success": "Votre configuration a été mise à jour avec succès.",
|
||||
"configuration_updated_error": "Quelque chose s'est mal déroulé lors de la sauvegarde.",
|
||||
"verify_hub": "Vérifier vos paramètres Kerberos Hub.",
|
||||
"verify_hub_success": "Les paramètres Kerberos Hub sont vérifiés avec succès.",
|
||||
"verify_hub_error": "Quelque chose s'est mal déroulé lors de la vérification Kerberos Hub",
|
||||
"verify_persistence": "Vérifier vos paramètres de persistance.",
|
||||
"verify_persistence_success": "Les paramètres de persistance sont vérifiés avec succès.",
|
||||
"verify_persistence_error": "Quelque chose s'est mal déroulé lors de la vérification de la persistance",
|
||||
"verify_camera": "Vérifier les paramètres de votre caméra.",
|
||||
"verify_camera_success": "Les paramètres de la caméra sont vérifiés avec succès.",
|
||||
"verify_camera_error": "Quelque chose s'est mal déroulé lors de la vérification des paramètres de la caméra"
|
||||
},
|
||||
"overview": {
|
||||
"general": "Général",
|
||||
"description_general": "Paramètres généraux pour votre Agent Kerberos",
|
||||
"key": "Clé",
|
||||
"camera_name": "Nom de la caméra",
|
||||
"timezone": "Fuseau horaire",
|
||||
"select_timezone": "Sélectionner un fuseau horaire",
|
||||
"advanced_configuration": "Configuration avancée",
|
||||
"description_advanced_configuration": "Les options de configuration détaillées pour activer ou désactiver des composants spécifiques de l'Agent Kerberos",
|
||||
"offline_mode": "Mode hors-ligne",
|
||||
"description_offline_mode": "Désactiver tout le trafic sortant"
|
||||
},
|
||||
"camera": {
|
||||
"camera": "Caméra",
|
||||
"description_camera": "Les paramètres de la caméra sont requis pour établir une connexion à la caméra de votre choix.",
|
||||
"only_h264": "Actuellement, seuls les flux RTSP H264 sont pris en charge.",
|
||||
"rtsp_url": "URL RTSP",
|
||||
"rtsp_h264": "Une connexion RTSP H264 à votre caméra.",
|
||||
"sub_rtsp_url": "URL RTSP secondaire (utilisé pour le direct)",
|
||||
"sub_rtsp_h264": "Une connexion RTSP secondaire vers le flux basse résolution de votre caméra.",
|
||||
"onvif": "ONVIF",
|
||||
"description_onvif": "Les identifiants pour communiquer avec les fonctionnalités ONVIF. Ils sont utilisés pour les fonctionnalités PTZ ou autres fournies par la caméra.",
|
||||
"onvif_xaddr": "Xaddr ONVIF",
|
||||
"onvif_username": "Nom d'utilisateur ONVIF",
|
||||
"onvif_password": "Mot de passe ONVIF",
|
||||
"verify_connection": "Vérifier la connexion",
|
||||
"verify_sub_connection": "Vérifier la connexion secondaire"
|
||||
},
|
||||
"recording": {
|
||||
"recording": "Enregistrement",
|
||||
"description_recording": "Préciser comment vous souhaitez réaliser les enregistrements, soit en ayant une installation enregistrant en continue 24 h/24 ou bien basée sur la détection de mouvement.",
|
||||
"continuous_recording": "Enregistrement continu",
|
||||
"description_continuous_recording": "Enregistrements 24 h/24 ou basés sur la détection de mouvement.",
|
||||
"max_duration": "Durée maximale de la vidéo (en secondes)",
|
||||
"description_max_duration": "La durée maximale d'un enregistrement.",
|
||||
"pre_recording": "Pré-enregistrement (images mises en cache)",
|
||||
"description_pre_recording": "Nombre de secondes avant qu'un événement ne se produise.",
|
||||
"post_recording": "Post-enregistrement (en secondes)",
|
||||
"description_post_recording": "Nombre de secondes après qu'un événement ne se produise.",
|
||||
"threshold": "Seuil de déclenchement (en pixels)",
|
||||
"description_threshold": "Le nombre de pixels ayant changé pour pour enregistrer",
|
||||
"autoclean": "Nettoyage automatique",
|
||||
"description_autoclean": "Préciser si l'Agent Kerberos peut supprimer des enregistrements lorsqu'une limite définie de stockage est atteinte (en Mo). Cela supprimera les enregistrements plus anciens lorsque la capacité sera atteinte.",
|
||||
"autoclean_enable": "Activer le nettoyage automatique",
|
||||
"autoclean_description_enable": "Supprimer les enregistrements plus anciens lorsque cette capacité est atteinte.",
|
||||
"autoclean_max_directory_size": "Taille maximale du répertoire (en Mo)",
|
||||
"autoclean_description_max_directory_size": "Le nombre maximum de Mo d'enregistrements stockés.",
|
||||
"fragmentedrecordings": "Enregistrements fragmentés",
|
||||
"description_fragmentedrecordings": "Lorsque les enregistrements sont fragmentés, ils conviennent à un flux HLS. A l'activation, le conteneur MP4 aura un aspect un peu différent.",
|
||||
"fragmentedrecordings_enable": "Activer la fragmentation",
|
||||
"fragmentedrecordings_description_enable": "Les enregistrements fragmentés sont requis pour le HLS.",
|
||||
"fragmentedrecordings_duration": "Durée du fragment",
|
||||
"fragmentedrecordings_description_duration": "Durée d'un seul fragment."
|
||||
},
|
||||
"streaming": {
|
||||
"stun_turn": "STUN/TURN pour WebRTC",
|
||||
"description_stun_turn": "Pour la diffusion continue en résolution intégrale nous utilisons le concept de WebRTC. Une des principales fonctionnalités est l'ICE-candidate, qui permet de traverser le NAT en utilisant les concepts de STUN/TURN.",
|
||||
"stun_server": "Serveur STUN",
|
||||
"turn_server": "Serveur TURN",
|
||||
"turn_username": "Nom d'utilisateur",
|
||||
"turn_password": "Mot de passe",
|
||||
"stun_turn_forward": "Redirection et transcodage",
|
||||
"stun_turn_description_forward": "Optimisations et améliorations pour la communication TURN/STUN.",
|
||||
"stun_turn_webrtc": "Redirection pour l'agent WebRTC",
|
||||
"stun_turn_description_webrtc": "Rediriger le flux H264 via MQTT",
|
||||
"stun_turn_transcode": "Transcoder le flux",
|
||||
"stun_turn_description_transcode": "Convertir le flux à une résolution inférieure",
|
||||
"stun_turn_downscale": "Réduction de la résolution (en % ou résolution originale)",
|
||||
"mqtt": "MQTT",
|
||||
"description_mqtt": "Un agent de messages MQTT est utilisé pour communiquer depuis",
|
||||
"description2_mqtt": "jusqu'à l'Agent Kerberos afin de permettre, par exemple, la diffusion continue ou des fonctionnalités ONVIF (PTZ).",
|
||||
"mqtt_brokeruri": "Uri de l'agent de messages",
|
||||
"mqtt_username": "Nom d'utilisateur",
|
||||
"mqtt_password": "Mot de passe"
|
||||
},
|
||||
"conditions": {
|
||||
"timeofinterest": "Période d'intérêt",
|
||||
"description_timeofinterest": "Effectuer des enregistrements uniquement pendant un intervalle de temps défini (basé sur le fuseau horaire).",
|
||||
"timeofinterest_enabled": "Activer",
|
||||
"timeofinterest_description_enabled": "Si activé, vous pouvez préciser une plage horaire",
|
||||
"sunday": "Dimanche",
|
||||
"monday": "Lundi",
|
||||
"tuesday": "Mardi",
|
||||
"wednesday": "Mercredi",
|
||||
"thursday": "Jeudi",
|
||||
"friday": "Vendredi",
|
||||
"saturday": "Samedi",
|
||||
"externalcondition": "Condition externe",
|
||||
"description_externalcondition": "En fonction d'un service Web externe, l'enregistrement peut-être activé ou désactivé.",
|
||||
"regionofinterest": "Zone d'intérêt",
|
||||
"description_regionofinterest": "En définissant une ou plusieurs zones, la détection de mouvement ne se fera qu'uniquement dans les zones que vous avez définies."
|
||||
},
|
||||
"persistence": {
|
||||
"kerberoshub": "Kerberos Hub",
|
||||
"description_kerberoshub": "Les Agents Kerberos peuvent envoyés des signaux de présence vers une installation centrale",
|
||||
"description2_kerberoshub": ". Les signaux de présence et d'autres informations pertinentes sont synchronisés vers Kerberos Hub pour fournir des informations en temps réel de votre environnement vidéo.",
|
||||
"persistence": "Persistance",
|
||||
"saasoffering": "Kerberos Hub (offre SAAS)",
|
||||
"description_persistence": "Avoir la possibilité de stocker vos enregistrements est le commencement de tout. Vous pouvez choisir entre notre",
|
||||
"description2_persistence": " ou auprès d'un fournisseur tiers",
|
||||
"select_persistence": "Sélectionner une persistance",
|
||||
"kerberoshub_proxyurl": "URL du proxy Kerberos Hub",
|
||||
"kerberoshub_description_proxyurl": "Le point de terminaison du proxy pour téléverser vos enregistrements.",
|
||||
"kerberoshub_apiurl": "URL de l'API Kerberos Hub",
|
||||
"kerberoshub_description_apiurl": "Le point de terminaison de l'API pour téléverser vos enregistrements.",
|
||||
"kerberoshub_publickey": "Clé publique",
|
||||
"kerberoshub_description_publickey": "La clé publique attribuée à votre compte Kerberos Hub.",
|
||||
"kerberoshub_privatekey": "Clé privée",
|
||||
"kerberoshub_description_privatekey": "La clé privée attribuée à votre compte Kerberos Hub.",
|
||||
"kerberoshub_site": "Site",
|
||||
"kerberoshub_description_site": "L'identifiant (ID) du site auquel appartiennent les Agents Kerberos dans Kerberos Hub.",
|
||||
"kerberoshub_region": "Région",
|
||||
"kerberoshub_description_region": "La région dans laquelle nous stockons vos enregistrements.",
|
||||
"kerberoshub_bucket": "Compartiment",
|
||||
"kerberoshub_description_bucket": "Le compartiment dans lequel nous stockons vos enregistrements.",
|
||||
"kerberoshub_username": "Utilisateur/Répertoire",
|
||||
"kerberoshub_description_username": "Le nom d'utilisateur de votre compte Kerberos Hub.",
|
||||
"kerberosvault_apiurl": "URL de l'API Kerberos Vault",
|
||||
"kerberosvault_description_apiurl": "L'API Kerberos Vault",
|
||||
"kerberosvault_provider": "Fournisseur",
|
||||
"kerberosvault_description_provider": "Le fournisseur auquel vos enregistrements seront envoyés.",
|
||||
"kerberosvault_directory": "Répertoire",
|
||||
"kerberosvault_description_directory": "Le sous-répertoire dans lequel les enregistrements seront stockés chez votre fournisseur.",
|
||||
"kerberosvault_accesskey": "Clé d'accès",
|
||||
"kerberosvault_description_accesskey": "La clé d'accès de votre compte Kerberos Vault.",
|
||||
"kerberosvault_secretkey": "Clé secrète",
|
||||
"kerberosvault_description_secretkey": "La clé secrète de votre compte Kerberos Vault.",
|
||||
"verify_connection": "Vérifier la connexion"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"watch_recordings": "Bekijk opnames",
|
||||
"configure": "Instellingen"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Opslaan"
|
||||
},
|
||||
"navigation": {
|
||||
"profile": "Profiel",
|
||||
"admin": "beheerder",
|
||||
@@ -36,5 +39,167 @@
|
||||
"time": "Tijd",
|
||||
"description": "Omschrijving",
|
||||
"name": "Naam"
|
||||
},
|
||||
"recordings": {
|
||||
"title": "Opnames",
|
||||
"heading": "Al je opnames in een eenvoudig overzicht",
|
||||
"search_media": "Zoek video"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Instellingen",
|
||||
"heading": "Configureer je camera",
|
||||
"submenu": {
|
||||
"all": "Alle",
|
||||
"overview": "Overzicht",
|
||||
"camera": "Camera",
|
||||
"recording": "Opnemen",
|
||||
"streaming": "Streaming",
|
||||
"conditions": "Condities",
|
||||
"persistence": "Opslag"
|
||||
},
|
||||
"info": {
|
||||
"kerberos_hub_demo": "Bekijk onze Kerberos Hub demo omgeving om een beter inzicht te verkrijgen!",
|
||||
"configuration_updated_success": "De configuratie is succesvol opgeslagen.",
|
||||
"configuration_updated_error": "Er ging iets fout tijdens het opslaan.",
|
||||
"verify_hub": "We controleren jouw Kerberos Hub instellingen.",
|
||||
"verify_hub_success": "De Kerberos Hub instellingen zijn goed bevonden.",
|
||||
"verify_hub_error": "Er ging iets fout tijdens het controleren van Kerberos Hub",
|
||||
"verify_persistence": "Controleren van de opslag instellingen.",
|
||||
"verify_persistence_success": "De opslag instellingen zijn goed bevonden",
|
||||
"verify_persistence_error": "Er ging iets fout tijdens het controleren van de opslag instellingen",
|
||||
"verify_camera": "We controleren de camera instellingen.",
|
||||
"verify_camera_success": "De camera instellingen zijn gevalideerd.",
|
||||
"verify_camera_error": "Er ging iets mis met de camera instellingen."
|
||||
},
|
||||
"overview": {
|
||||
"general": "Algemeen",
|
||||
"description_general": "Algemene instellingen voor jouw Kerberos Agent",
|
||||
"key": "Key",
|
||||
"camera_name": "Camera naam",
|
||||
"timezone": "Tijdzone",
|
||||
"select_timezone": "Selecteer uw tijdzone",
|
||||
"advanced_configuration": "Geavanceerde instellingen",
|
||||
"description_advanced_configuration": "Detail instellingen om bepaalde functionaliteiten van je Kerberos Agent aan en uit te zetten",
|
||||
"offline_mode": "Offline modus",
|
||||
"description_offline_mode": "Uitzetten van uitgaande connectiviteit"
|
||||
},
|
||||
"camera": {
|
||||
"camera": "Camera",
|
||||
"description_camera": "Camera settings are required to make a connection to your camera of choice.",
|
||||
"only_h264": "Momenteel worden enkel H264 RTSP streams gesupporteerd",
|
||||
"rtsp_url": "RTSP url",
|
||||
"rtsp_h264": "Een H264 RTSP connectie met jouw camera.",
|
||||
"sub_rtsp_url": "Sub RTSP url (used for livestreaming)",
|
||||
"sub_rtsp_h264": "A secondary RTSP connection to the low resolution of your camera.",
|
||||
"onvif": "ONVIF",
|
||||
"description_onvif": "Gegevens om te communiceren met ONVIF vaardigheden. Deze worden gebruikt voor PTZ of andere vaardigheden van de camera.",
|
||||
"onvif_xaddr": "ONVIF xaddr",
|
||||
"onvif_username": "ONVIF gebruikersnaam",
|
||||
"onvif_password": "ONVIF wachtwoord",
|
||||
"verify_connection": "Verifieer verbinding",
|
||||
"verify_sub_connection": "Verify Sub Connection"
|
||||
},
|
||||
"recording": {
|
||||
"recording": "Opname",
|
||||
"description_recording": "Detailleer hoe je opnames wilt maken. Bv. doorlopende 24/7 opnames of bewegings detectie opnames.",
|
||||
"continuous_recording": "Doorlopende opnames",
|
||||
"description_continuous_recording": "Maak 24/7 of bewegings opnames.",
|
||||
"max_duration": "Maximaal video duratie (seconden)",
|
||||
"description_max_duration": "De maximale duur van een opname.",
|
||||
"pre_recording": "voor opname (key frames buffered)",
|
||||
"description_pre_recording": "Aantal seconden voor een beweging gedetecteerd is",
|
||||
"post_recording": "na opname (seconden)",
|
||||
"description_post_recording": "Aantal seconden na een beweging gedetecteerd is",
|
||||
"threshold": "Opname drempel (pixels)",
|
||||
"description_threshold": "Het aantal pixel veranderingen voor een opname",
|
||||
"autoclean": "Automatische opruiming",
|
||||
"description_autoclean": "De Kerberos Agent kan opnames verwijderen wanneer een opslag limiet bereikt is. Indien het geval worden de oudste opnames eerst verwijderd",
|
||||
"autoclean_enable": "Zet automatische verwijdering aan",
|
||||
"autoclean_description_enable": "Verwijder oudste opnames wanneer limiet bereikt is",
|
||||
"autoclean_max_directory_size": "Maximale map opslag (MB)",
|
||||
"autoclean_description_max_directory_size": "De maximale video opslag (MB's.)",
|
||||
"fragmentedrecordings": "Gefragmenteerde opnames",
|
||||
"description_fragmentedrecordings": "Wanneer opnames gefragmenteerd zijn, zijn deze toegankelijk voor een HLS stream. Wanneer geactiveerd zal de MP4 container er iets anders uitzien.",
|
||||
"fragmentedrecordings_enable": "Zet fragmentatie aan",
|
||||
"fragmentedrecordings_description_enable": "Fragmenteerde opnames zijn noodzakelijk voor HLS.",
|
||||
"fragmentedrecordings_duration": "fragment duratie",
|
||||
"fragmentedrecordings_description_duration": "Duratie van een fragment.",
|
||||
"verify_connection": "Verifieer Connectie"
|
||||
},
|
||||
"streaming": {
|
||||
"stun_turn": "STUN/TURN voor WebRTC",
|
||||
"description_stun_turn": "Voor een hoge definitie live weergave, wordt WebRTC toegepast. Een van de vaardigheden is de toepassing van ICE-candidate, welke NAT traversal toelaat doormiddel van STUN/TURN.",
|
||||
"stun_server": "STUN server",
|
||||
"turn_server": "TURN server",
|
||||
"turn_username": "Gebruikersnaam",
|
||||
"turn_password": "Wachtwoord",
|
||||
"stun_turn_forward": "Doorsturen en transcoden",
|
||||
"stun_turn_description_forward": "Optimalisatie en verbetering voor TURN/STUN communicatie.",
|
||||
"stun_turn_webrtc": "Doorsturen naar een WebRTC broker",
|
||||
"stun_turn_description_webrtc": "Doorsturen van H264 stream naar MQTT",
|
||||
"stun_turn_transcode": "Transcode stream",
|
||||
"stun_turn_description_transcode": "Omzetten van stream naar een lagere resolutie",
|
||||
"stun_turn_downscale": "Neerschalen resolutie (in % van originale resolutie)",
|
||||
"mqtt": "MQTT",
|
||||
"description_mqtt": "Een MQTT broker wordt gebruikt om te communiceren van",
|
||||
"description2_mqtt": "naar de Kerberos Agent, om te genieten van livestreaming of ONVIF (PTZ) vaardigheden.",
|
||||
"mqtt_brokeruri": "Broker Uri",
|
||||
"mqtt_username": "Gebruikersnaam",
|
||||
"mqtt_password": "Wachtworod"
|
||||
},
|
||||
"conditions": {
|
||||
"timeofinterest": "Tijds interval",
|
||||
"description_timeofinterest": "Maken van opnames tussen twee tijdstippen (gebasseerd op tijdszone).",
|
||||
"timeofinterest_enabled": "Geactiveerd",
|
||||
"timeofinterest_description_enabled": "Wanneer geactiveerd kan je een tijdsinterval instellen",
|
||||
"sunday": "Zondag",
|
||||
"monday": "Maandag",
|
||||
"tuesday": "Dinsdag",
|
||||
"wednesday": "Woensdag",
|
||||
"thursday": "Donderdag",
|
||||
"friday": "Vrijdag",
|
||||
"saturday": "Zaterdag",
|
||||
"externalcondition": "Externe Condities",
|
||||
"description_externalcondition": "Afhankelijk van een externe webservice opnames zijn geactiveerd of niet geactiveerd.",
|
||||
"regionofinterest": "Bereik interval",
|
||||
"description_regionofinterest": "Door het instellen van een of meerdere regios wordt beweging enkel gedetecteerd in de vooropgestelde regios."
|
||||
},
|
||||
"persistence": {
|
||||
"kerberoshub": "Kerberos Hub",
|
||||
"description_kerberoshub": "Kerberos Agents sturen een bericht naar een centrale",
|
||||
"description2_kerberoshub": "installatie. Berichten en andere relevante informatie worden gesynced naar Kerberos Hub on zo een real-tive overzicht te geven van jouw video landschap.",
|
||||
"persistence": "Opslag",
|
||||
"saasoffering": "Kerberos Hub (SAAS)",
|
||||
"description_persistence": "De mogelijkheid om jouw opnames op te slaan is het begin van alles. Je kan kiezen tussen ons",
|
||||
"description2_persistence": ", of een 3rd party provider",
|
||||
"select_persistence": "Selecteer een opslagmethode",
|
||||
"kerberoshub_proxyurl": "Kerberos Hub Proxy URL",
|
||||
"kerberoshub_description_proxyurl": "De Proxy url voor het opladen van jouw opnames.",
|
||||
"kerberoshub_apiurl": "Kerberos Hub API URL",
|
||||
"kerberoshub_description_apiurl": "De API endpoint om jouw opnames op te laden.",
|
||||
"kerberoshub_publickey": "Publieke sleutel",
|
||||
"kerberoshub_description_publickey": "De publieke sleutel voor jouw Kerberos Hub account.",
|
||||
"kerberoshub_privatekey": "Private key",
|
||||
"kerberoshub_description_privatekey": "De private sleutel voor jouw Kerberos Hub account.",
|
||||
"kerberoshub_site": "Site",
|
||||
"kerberoshub_description_site": "De site ID waar je Kerberos Agents toebehoort in Kerberos Hub.",
|
||||
"kerberoshub_region": "Regio",
|
||||
"kerberoshub_description_region": "De regio waar jouw opnames worden opgeslagen.",
|
||||
"kerberoshub_bucket": "Bucket",
|
||||
"kerberoshub_description_bucket": "De bucket opslag locatie",
|
||||
"kerberoshub_username": "Gebruikersnaam/Map",
|
||||
"kerberoshub_description_username": "De gebruikersnaam van jouw Kerberos Hub account.",
|
||||
"kerberosvault_apiurl": "Kerberos Vault API URL",
|
||||
"kerberosvault_description_apiurl": "De Kerberos Vault API",
|
||||
"kerberosvault_provider": "Provider",
|
||||
"kerberosvault_description_provider": "De provider verantwoordelijk voor de opnames op te slaan.",
|
||||
"kerberosvault_directory": "Map",
|
||||
"kerberosvault_description_directory": "Sub map waarin de opnames worden opgeslagen.",
|
||||
"kerberosvault_accesskey": "Access key",
|
||||
"kerberosvault_description_accesskey": "De access key van jouw Kerberos Vault account.",
|
||||
"kerberosvault_secretkey": "Secret key",
|
||||
"kerberosvault_description_secretkey": "De secret key van jouw Kerberos Vault account.",
|
||||
"verify_connection": "Verifieer verbinding"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,204 @@
|
||||
{
|
||||
"breadcrumb": {
|
||||
"watch_recordings": "Watch recordings",
|
||||
"configure": "Configure"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Save"
|
||||
},
|
||||
"navigation": {
|
||||
"profile": "",
|
||||
"admin": "",
|
||||
"management": "",
|
||||
"dashboard": "",
|
||||
"recordings": "",
|
||||
"settings": "",
|
||||
"help_support": "",
|
||||
"swagger": "",
|
||||
"documentation": "",
|
||||
"ui_library": "",
|
||||
"layout": ""
|
||||
"profile": "Profil",
|
||||
"admin": "admin",
|
||||
"management": "Kierownictwo",
|
||||
"dashboard": "Panel",
|
||||
"recordings": "Nagrania",
|
||||
"settings": "Ustawienia",
|
||||
"help_support": "Pomoc wsparcie",
|
||||
"swagger": "Swagger API",
|
||||
"documentation": "Dokumentacja",
|
||||
"ui_library": "UI Biblioteka",
|
||||
"layout": "Język i układ",
|
||||
"choose_language": "Wybierz język"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"heading": "Overview of your video surveilance",
|
||||
"number_of_days": "Number of days",
|
||||
"total_recordings": "Total recordings",
|
||||
"connected": "Connected",
|
||||
"not_connected": "Not connected",
|
||||
"offline_mode": "Offline mode",
|
||||
"latest_events": "Latest events",
|
||||
"configure_connection": "Configure connection",
|
||||
"no_events": "No events",
|
||||
"no_events_description": "No recordings where found, make sure your Kerberos Agent is properly configured.",
|
||||
"motion_detected": "Motion was detected",
|
||||
"live_view": "Live view",
|
||||
"loading_live_view": "Loading live view",
|
||||
"loading_live_view_description": "Hold on we are loading your live view here. If you didn't configure your camera connection, update it on the settings pages.",
|
||||
"time": "Time",
|
||||
"description": "Description",
|
||||
"name": "Name"
|
||||
},
|
||||
"recordings": {
|
||||
"title": "Recordings",
|
||||
"heading": "All your recordings in a single place",
|
||||
"search_media": "Search media"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"heading": "Onboard your camera",
|
||||
"submenu": {
|
||||
"all": "All",
|
||||
"overview": "Overview",
|
||||
"camera": "Camera",
|
||||
"recording": "Recording",
|
||||
"streaming": "Streaming",
|
||||
"conditions": "Conditions",
|
||||
"persistence": "Persistence"
|
||||
},
|
||||
"info": {
|
||||
"kerberos_hub_demo": "Have a look at our Kerberos Hub demo environment, to see Kerberos Hub in action!",
|
||||
"configuration_updated_success": "Your configuration have been updated successfully.",
|
||||
"configuration_updated_error": "Something went wrong while saving.",
|
||||
"verify_hub": "Verifying your Kerberos Hub settings.",
|
||||
"verify_hub_success": "Kerberos Hub settings are successfully verified.",
|
||||
"verify_hub_error": "Something went wrong while verifying Kerberos Hub",
|
||||
"verify_persistence": "Verifying your persistence settings.",
|
||||
"verify_persistence_success": "Persistence settings are successfully verified.",
|
||||
"verify_persistence_error": "Something went wrong while verifying the persistence",
|
||||
"verify_camera": "Verifying your camera settings.",
|
||||
"verify_camera_success": "Camera settings are successfully verified.",
|
||||
"verify_camera_error": "Something went wrong while verifying the camera settings"
|
||||
},
|
||||
"overview": {
|
||||
"general": "General",
|
||||
"description_general": "General settings for your Kerberos Agent",
|
||||
"key": "Key",
|
||||
"camera_name": "Camera name",
|
||||
"timezone": "Timezone",
|
||||
"select_timezone": "Select a timezone",
|
||||
"advanced_configuration": "Advanced configuration",
|
||||
"description_advanced_configuration": "Detailed configuration options to enable or disable specific parts of the Kerberos Agent",
|
||||
"offline_mode": "Offline mode",
|
||||
"description_offline_mode": "Disable all outgoing traffic"
|
||||
},
|
||||
"camera": {
|
||||
"camera": "Camera",
|
||||
"description_camera": "Camera settings are required to make a connection to your camera of choice.",
|
||||
"only_h264": "Currently only H264 RTSP streams are supported.",
|
||||
"rtsp_url": "RTSP url",
|
||||
"rtsp_h264": "A H264 RTSP connection to your camera.",
|
||||
"sub_rtsp_url": "Sub RTSP url (used for livestreaming)",
|
||||
"sub_rtsp_h264": "A secondary RTSP connection to the low resolution of your camera.",
|
||||
"onvif": "ONVIF",
|
||||
"description_onvif": "Credentials to communicate with ONVIF capabilities. These are used for PTZ or other capabilities provided by the camera.",
|
||||
"onvif_xaddr": "ONVIF xaddr",
|
||||
"onvif_username": "ONVIF username",
|
||||
"onvif_password": "ONVIF password",
|
||||
"verify_connection": "Verify Connection",
|
||||
"verify_sub_connection": "Verify Sub Connection"
|
||||
},
|
||||
"recording": {
|
||||
"recording": "Recording",
|
||||
"description_recording": "Specify how you would like to make recordings. Having a continuous 24/7 setup or a motion based recording.",
|
||||
"continuous_recording": "Continuous recording",
|
||||
"description_continuous_recording": "Make 24/7 or motion based recordings.",
|
||||
"max_duration": "max video duration (seconds)",
|
||||
"description_max_duration": "The maximum duration of a recording.",
|
||||
"pre_recording": "pre recording (key frames buffered)",
|
||||
"description_pre_recording": "Seconds before an event occurred.",
|
||||
"post_recording": "post recording (seconds)",
|
||||
"description_post_recording": "Seconds after an event occurred.",
|
||||
"threshold": "Recording threshold (pixels)",
|
||||
"description_threshold": "The number of pixels changed to record",
|
||||
"autoclean": "Auto clean",
|
||||
"description_autoclean": "Specify if the Kerberos Agent can cleanup recordings when a specific storage capacity (MB) is reached. This will remove the oldest recordings when the capacity is reached.",
|
||||
"autoclean_enable": "Enable auto clean",
|
||||
"autoclean_description_enable": "Remove oldest recording when capacity reached.",
|
||||
"autoclean_max_directory_size": "Maximum directory size (MB)",
|
||||
"autoclean_description_max_directory_size": "The maximum MB's of recordings stored.",
|
||||
"fragmentedrecordings": "Fragmented recordings",
|
||||
"description_fragmentedrecordings": "When recordings are fragmented they are suitable for an HLS stream. When turned on the MP4 container will look a bit different.",
|
||||
"fragmentedrecordings_enable": "Enable fragmentation",
|
||||
"fragmentedrecordings_description_enable": "Fragmented recordings are required for HLS.",
|
||||
"fragmentedrecordings_duration": "fragment duration",
|
||||
"fragmentedrecordings_description_duration": "Duration of a single fragment."
|
||||
},
|
||||
"streaming": {
|
||||
"stun_turn": "STUN/TURN for WebRTC",
|
||||
"description_stun_turn": "For full-resolution livestreaming we use the concept of WebRTC. One of the key capabilities is the ICE-candidate feature, which allows NAT traversal using the concepts of STUN/TURN.",
|
||||
"stun_server": "STUN server",
|
||||
"turn_server": "TURN server",
|
||||
"turn_username": "Username",
|
||||
"turn_password": "Password",
|
||||
"stun_turn_forward": "Forwarding and transcoding",
|
||||
"stun_turn_description_forward": "Optimisations and enhancements for TURN/STUN communication.",
|
||||
"stun_turn_webrtc": "Forwarding to WebRTC broker",
|
||||
"stun_turn_description_webrtc": "Forward h264 stream through MQTT",
|
||||
"stun_turn_transcode": "Transcode stream",
|
||||
"stun_turn_description_transcode": "Convert stream to a lower resolution",
|
||||
"stun_turn_downscale": "Downscale resolution (in % or original resolution)",
|
||||
"mqtt": "MQTT",
|
||||
"description_mqtt": "A MQTT broker is used to communicate from",
|
||||
"description2_mqtt": "to the Kerberos Agent, to achieve for example livestreaming or ONVIF (PTZ) capabilities.",
|
||||
"mqtt_brokeruri": "Broker Uri",
|
||||
"mqtt_username": "Username",
|
||||
"mqtt_password": "Password"
|
||||
},
|
||||
"conditions": {
|
||||
"timeofinterest": "Time Of Interest",
|
||||
"description_timeofinterest": "Only make recordings between specific time intervals (based on timezone).",
|
||||
"timeofinterest_enabled": "Enabled",
|
||||
"timeofinterest_description_enabled": "If enabled you can specify time windows",
|
||||
"sunday": "Sunday",
|
||||
"monday": "Monday",
|
||||
"tuesday": "Tuesday",
|
||||
"wednesday": "Wednesday",
|
||||
"thursday": "Thursday",
|
||||
"friday": "Friday",
|
||||
"saturday": "Saturday",
|
||||
"externalcondition": "External Condition",
|
||||
"description_externalcondition": "Depending on an external webservice recording can be enabled or disabled.",
|
||||
"regionofinterest": "Region Of Interest",
|
||||
"description_regionofinterest": "By defining one or more regions, motion will be tracked only in the regions you have defined."
|
||||
},
|
||||
"persistence": {
|
||||
"kerberoshub": "Kerberos Hub",
|
||||
"description_kerberoshub": "Kerberos Agents can send heartbeats to a central",
|
||||
"description2_kerberoshub": "installation. Heartbeats and other relevant information are synced to Kerberos Hub to show realtime information about your video landscape.",
|
||||
"persistence": "Persistence",
|
||||
"saasoffering": "Kerberos Hub (SAAS offering)",
|
||||
"description_persistence": "Having the ability to store your recordings is the beginning of everything. You can choose between our",
|
||||
"description2_persistence": ", or a 3rd party provider",
|
||||
"select_persistence": "Select a persistence",
|
||||
"kerberoshub_proxyurl": "Kerberos Hub Proxy URL",
|
||||
"kerberoshub_description_proxyurl": "The Proxy endpoint for uploading your recordings.",
|
||||
"kerberoshub_apiurl": "Kerberos Hub API URL",
|
||||
"kerberoshub_description_apiurl": "The API endpoint for uploading your recordings.",
|
||||
"kerberoshub_publickey": "Public key",
|
||||
"kerberoshub_description_publickey": "The public key granted to your Kerberos Hub account.",
|
||||
"kerberoshub_privatekey": "Private key",
|
||||
"kerberoshub_description_privatekey": "The private key granted to your Kerberos Hub account.",
|
||||
"kerberoshub_site": "Site",
|
||||
"kerberoshub_description_site": "The site ID the Kerberos Agents are belonging to in Kerberos Hub.",
|
||||
"kerberoshub_region": "Region",
|
||||
"kerberoshub_description_region": "The region we are storing our recordings in.",
|
||||
"kerberoshub_bucket": "Bucket",
|
||||
"kerberoshub_description_bucket": "The bucket we are storing our recordings in.",
|
||||
"kerberoshub_username": "Username/Directory",
|
||||
"kerberoshub_description_username": "The username of your Kerberos Hub account.",
|
||||
"kerberosvault_apiurl": "Kerberos Vault API URL",
|
||||
"kerberosvault_description_apiurl": "The Kerberos Vault API",
|
||||
"kerberosvault_provider": "Provider",
|
||||
"kerberosvault_description_provider": "The provider to which your recordings will be send.",
|
||||
"kerberosvault_directory": "Directory",
|
||||
"kerberosvault_description_directory": "Sub directory the recordings will be stored in your provider.",
|
||||
"kerberosvault_accesskey": "Access key",
|
||||
"kerberosvault_description_accesskey": "The access key of your Kerberos Vault account.",
|
||||
"kerberosvault_secretkey": "Secret key",
|
||||
"kerberosvault_description_secretkey": "The secret key of your Kerberos Vault account.",
|
||||
"verify_connection": "Verify Connection"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
204
ui/public/locales/pt/translation.json
Normal file
204
ui/public/locales/pt/translation.json
Normal file
@@ -0,0 +1,204 @@
|
||||
{
|
||||
"breadcrumb": {
|
||||
"watch_recordings": "Watch recordings",
|
||||
"configure": "Configure"
|
||||
},
|
||||
"buttons": {
|
||||
"save": "Save"
|
||||
},
|
||||
"navigation": {
|
||||
"profile": "Perfil",
|
||||
"admin": "admin",
|
||||
"management": "Gestão",
|
||||
"dashboard": "Painel",
|
||||
"recordings": "Gravações",
|
||||
"settings": "Definições",
|
||||
"help_support": "Ajuda e suporte",
|
||||
"swagger": "Swagger API",
|
||||
"documentation": "Documentação",
|
||||
"ui_library": "UI Biblioteca",
|
||||
"layout": "Idioma e layout",
|
||||
"choose_language": "Escolha o seu idioma"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"heading": "Overview of your video surveilance",
|
||||
"number_of_days": "Number of days",
|
||||
"total_recordings": "Total recordings",
|
||||
"connected": "Connected",
|
||||
"not_connected": "Not connected",
|
||||
"offline_mode": "Offline mode",
|
||||
"latest_events": "Latest events",
|
||||
"configure_connection": "Configure connection",
|
||||
"no_events": "No events",
|
||||
"no_events_description": "No recordings where found, make sure your Kerberos Agent is properly configured.",
|
||||
"motion_detected": "Motion was detected",
|
||||
"live_view": "Live view",
|
||||
"loading_live_view": "Loading live view",
|
||||
"loading_live_view_description": "Hold on we are loading your live view here. If you didn't configure your camera connection, update it on the settings pages.",
|
||||
"time": "Time",
|
||||
"description": "Description",
|
||||
"name": "Name"
|
||||
},
|
||||
"recordings": {
|
||||
"title": "Recordings",
|
||||
"heading": "All your recordings in a single place",
|
||||
"search_media": "Search media"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"heading": "Onboard your camera",
|
||||
"submenu": {
|
||||
"all": "All",
|
||||
"overview": "Overview",
|
||||
"camera": "Camera",
|
||||
"recording": "Recording",
|
||||
"streaming": "Streaming",
|
||||
"conditions": "Conditions",
|
||||
"persistence": "Persistence"
|
||||
},
|
||||
"info": {
|
||||
"kerberos_hub_demo": "Have a look at our Kerberos Hub demo environment, to see Kerberos Hub in action!",
|
||||
"configuration_updated_success": "Your configuration have been updated successfully.",
|
||||
"configuration_updated_error": "Something went wrong while saving.",
|
||||
"verify_hub": "Verifying your Kerberos Hub settings.",
|
||||
"verify_hub_success": "Kerberos Hub settings are successfully verified.",
|
||||
"verify_hub_error": "Something went wrong while verifying Kerberos Hub",
|
||||
"verify_persistence": "Verifying your persistence settings.",
|
||||
"verify_persistence_success": "Persistence settings are successfully verified.",
|
||||
"verify_persistence_error": "Something went wrong while verifying the persistence",
|
||||
"verify_camera": "Verifying your camera settings.",
|
||||
"verify_camera_success": "Camera settings are successfully verified.",
|
||||
"verify_camera_error": "Something went wrong while verifying the camera settings"
|
||||
},
|
||||
"overview": {
|
||||
"general": "General",
|
||||
"description_general": "General settings for your Kerberos Agent",
|
||||
"key": "Key",
|
||||
"camera_name": "Camera name",
|
||||
"timezone": "Timezone",
|
||||
"select_timezone": "Select a timezone",
|
||||
"advanced_configuration": "Advanced configuration",
|
||||
"description_advanced_configuration": "Detailed configuration options to enable or disable specific parts of the Kerberos Agent",
|
||||
"offline_mode": "Offline mode",
|
||||
"description_offline_mode": "Disable all outgoing traffic"
|
||||
},
|
||||
"camera": {
|
||||
"camera": "Camera",
|
||||
"description_camera": "Camera settings are required to make a connection to your camera of choice.",
|
||||
"only_h264": "Currently only H264 RTSP streams are supported.",
|
||||
"rtsp_url": "RTSP url",
|
||||
"rtsp_h264": "A H264 RTSP connection to your camera.",
|
||||
"sub_rtsp_url": "Sub RTSP url (used for livestreaming)",
|
||||
"sub_rtsp_h264": "A secondary RTSP connection to the low resolution of your camera.",
|
||||
"onvif": "ONVIF",
|
||||
"description_onvif": "Credentials to communicate with ONVIF capabilities. These are used for PTZ or other capabilities provided by the camera.",
|
||||
"onvif_xaddr": "ONVIF xaddr",
|
||||
"onvif_username": "ONVIF username",
|
||||
"onvif_password": "ONVIF password",
|
||||
"verify_connection": "Verify Connection",
|
||||
"verify_sub_connection": "Verify Sub Connection"
|
||||
},
|
||||
"recording": {
|
||||
"recording": "Recording",
|
||||
"description_recording": "Specify how you would like to make recordings. Having a continuous 24/7 setup or a motion based recording.",
|
||||
"continuous_recording": "Continuous recording",
|
||||
"description_continuous_recording": "Make 24/7 or motion based recordings.",
|
||||
"max_duration": "max video duration (seconds)",
|
||||
"description_max_duration": "The maximum duration of a recording.",
|
||||
"pre_recording": "pre recording (key frames buffered)",
|
||||
"description_pre_recording": "Seconds before an event occurred.",
|
||||
"post_recording": "post recording (seconds)",
|
||||
"description_post_recording": "Seconds after an event occurred.",
|
||||
"threshold": "Recording threshold (pixels)",
|
||||
"description_threshold": "The number of pixels changed to record",
|
||||
"autoclean": "Auto clean",
|
||||
"description_autoclean": "Specify if the Kerberos Agent can cleanup recordings when a specific storage capacity (MB) is reached. This will remove the oldest recordings when the capacity is reached.",
|
||||
"autoclean_enable": "Enable auto clean",
|
||||
"autoclean_description_enable": "Remove oldest recording when capacity reached.",
|
||||
"autoclean_max_directory_size": "Maximum directory size (MB)",
|
||||
"autoclean_description_max_directory_size": "The maximum MB's of recordings stored.",
|
||||
"fragmentedrecordings": "Fragmented recordings",
|
||||
"description_fragmentedrecordings": "When recordings are fragmented they are suitable for an HLS stream. When turned on the MP4 container will look a bit different.",
|
||||
"fragmentedrecordings_enable": "Enable fragmentation",
|
||||
"fragmentedrecordings_description_enable": "Fragmented recordings are required for HLS.",
|
||||
"fragmentedrecordings_duration": "fragment duration",
|
||||
"fragmentedrecordings_description_duration": "Duration of a single fragment."
|
||||
},
|
||||
"streaming": {
|
||||
"stun_turn": "STUN/TURN for WebRTC",
|
||||
"description_stun_turn": "For full-resolution livestreaming we use the concept of WebRTC. One of the key capabilities is the ICE-candidate feature, which allows NAT traversal using the concepts of STUN/TURN.",
|
||||
"stun_server": "STUN server",
|
||||
"turn_server": "TURN server",
|
||||
"turn_username": "Username",
|
||||
"turn_password": "Password",
|
||||
"stun_turn_forward": "Forwarding and transcoding",
|
||||
"stun_turn_description_forward": "Optimisations and enhancements for TURN/STUN communication.",
|
||||
"stun_turn_webrtc": "Forwarding to WebRTC broker",
|
||||
"stun_turn_description_webrtc": "Forward h264 stream through MQTT",
|
||||
"stun_turn_transcode": "Transcode stream",
|
||||
"stun_turn_description_transcode": "Convert stream to a lower resolution",
|
||||
"stun_turn_downscale": "Downscale resolution (in % or original resolution)",
|
||||
"mqtt": "MQTT",
|
||||
"description_mqtt": "A MQTT broker is used to communicate from",
|
||||
"description2_mqtt": "to the Kerberos Agent, to achieve for example livestreaming or ONVIF (PTZ) capabilities.",
|
||||
"mqtt_brokeruri": "Broker Uri",
|
||||
"mqtt_username": "Username",
|
||||
"mqtt_password": "Password"
|
||||
},
|
||||
"conditions": {
|
||||
"timeofinterest": "Time Of Interest",
|
||||
"description_timeofinterest": "Only make recordings between specific time intervals (based on timezone).",
|
||||
"timeofinterest_enabled": "Enabled",
|
||||
"timeofinterest_description_enabled": "If enabled you can specify time windows",
|
||||
"sunday": "Sunday",
|
||||
"monday": "Monday",
|
||||
"tuesday": "Tuesday",
|
||||
"wednesday": "Wednesday",
|
||||
"thursday": "Thursday",
|
||||
"friday": "Friday",
|
||||
"saturday": "Saturday",
|
||||
"externalcondition": "External Condition",
|
||||
"description_externalcondition": "Depending on an external webservice recording can be enabled or disabled.",
|
||||
"regionofinterest": "Region Of Interest",
|
||||
"description_regionofinterest": "By defining one or more regions, motion will be tracked only in the regions you have defined."
|
||||
},
|
||||
"persistence": {
|
||||
"kerberoshub": "Kerberos Hub",
|
||||
"description_kerberoshub": "Kerberos Agents can send heartbeats to a central",
|
||||
"description2_kerberoshub": "installation. Heartbeats and other relevant information are synced to Kerberos Hub to show realtime information about your video landscape.",
|
||||
"persistence": "Persistence",
|
||||
"saasoffering": "Kerberos Hub (SAAS offering)",
|
||||
"description_persistence": "Having the ability to store your recordings is the beginning of everything. You can choose between our",
|
||||
"description2_persistence": ", or a 3rd party provider",
|
||||
"select_persistence": "Select a persistence",
|
||||
"kerberoshub_proxyurl": "Kerberos Hub Proxy URL",
|
||||
"kerberoshub_description_proxyurl": "The Proxy endpoint for uploading your recordings.",
|
||||
"kerberoshub_apiurl": "Kerberos Hub API URL",
|
||||
"kerberoshub_description_apiurl": "The API endpoint for uploading your recordings.",
|
||||
"kerberoshub_publickey": "Public key",
|
||||
"kerberoshub_description_publickey": "The public key granted to your Kerberos Hub account.",
|
||||
"kerberoshub_privatekey": "Private key",
|
||||
"kerberoshub_description_privatekey": "The private key granted to your Kerberos Hub account.",
|
||||
"kerberoshub_site": "Site",
|
||||
"kerberoshub_description_site": "The site ID the Kerberos Agents are belonging to in Kerberos Hub.",
|
||||
"kerberoshub_region": "Region",
|
||||
"kerberoshub_description_region": "The region we are storing our recordings in.",
|
||||
"kerberoshub_bucket": "Bucket",
|
||||
"kerberoshub_description_bucket": "The bucket we are storing our recordings in.",
|
||||
"kerberoshub_username": "Username/Directory",
|
||||
"kerberoshub_description_username": "The username of your Kerberos Hub account.",
|
||||
"kerberosvault_apiurl": "Kerberos Vault API URL",
|
||||
"kerberosvault_description_apiurl": "The Kerberos Vault API",
|
||||
"kerberosvault_provider": "Provider",
|
||||
"kerberosvault_description_provider": "The provider to which your recordings will be send.",
|
||||
"kerberosvault_directory": "Directory",
|
||||
"kerberosvault_description_directory": "Sub directory the recordings will be stored in your provider.",
|
||||
"kerberosvault_accesskey": "Access key",
|
||||
"kerberosvault_description_accesskey": "The access key of your Kerberos Vault account.",
|
||||
"kerberosvault_secretkey": "Secret key",
|
||||
"kerberosvault_description_secretkey": "The secret key of your Kerberos Vault account.",
|
||||
"verify_connection": "Verify Connection"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import uuid from 'uuidv4';
|
||||
import {
|
||||
connect as connectWS,
|
||||
disconnect as disconnectWS,
|
||||
send,
|
||||
} from '@giantmachines/redux-websocket';
|
||||
import {
|
||||
Badge,
|
||||
Main,
|
||||
MainBody,
|
||||
Gradient,
|
||||
@@ -27,8 +34,9 @@ import './App.scss';
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
class App extends React.Component {
|
||||
componentDidMount() {
|
||||
const { dispatchGetDashboardInformation } = this.props;
|
||||
const { dispatchGetDashboardInformation, dispatchConnect } = this.props;
|
||||
dispatchGetDashboardInformation();
|
||||
dispatchConnect();
|
||||
|
||||
const interval$ = interval(5000);
|
||||
this.subscription = interval$.subscribe(() => {
|
||||
@@ -36,8 +44,33 @@ class App extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// We are connected again, lets fire the initial events.
|
||||
const { connected, dispatchSend, dispatchConnect } = this.props;
|
||||
const { connected: connectedPrev } = prevProps;
|
||||
if (connectedPrev === false && connected === true) {
|
||||
const message = {
|
||||
client_id: uuid(),
|
||||
message_type: 'hello',
|
||||
};
|
||||
dispatchSend(message);
|
||||
}
|
||||
|
||||
// We disconnected, let's try to connect again
|
||||
if (connectedPrev === true && connected === false) {
|
||||
dispatchConnect();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
const message = {
|
||||
client_id: uuid(),
|
||||
message_type: 'goodbye',
|
||||
};
|
||||
const { dispatchSend, dispatchDisconnect } = this.props;
|
||||
dispatchSend(message);
|
||||
dispatchDisconnect();
|
||||
}
|
||||
|
||||
getCurrentTimestamp() {
|
||||
@@ -45,7 +78,7 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { t, connected } = this.props;
|
||||
const { children, username, dashboard, dispatchLogout } = this.props;
|
||||
const cloudOnline = this.getCurrentTimestamp() - dashboard.cloudOnline < 30;
|
||||
return (
|
||||
@@ -113,6 +146,16 @@ class App extends React.Component {
|
||||
<NavigationGroup>
|
||||
<LanguageSelect />
|
||||
</NavigationGroup>
|
||||
|
||||
<NavigationSection title="Websocket" />
|
||||
<NavigationGroup>
|
||||
<div className="websocket-badge">
|
||||
<Badge
|
||||
title={connected ? 'connected' : 'disconnected'}
|
||||
status={connected ? 'success' : 'warning'}
|
||||
/>
|
||||
</div>
|
||||
</NavigationGroup>
|
||||
</Navigation>
|
||||
</Sidebar>
|
||||
<Main>
|
||||
@@ -151,10 +194,16 @@ class App extends React.Component {
|
||||
const mapStateToProps = (state) => ({
|
||||
username: state.authentication.username,
|
||||
dashboard: state.agent.dashboard,
|
||||
connected: state.wss.connected,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
dispatchLogout: () => dispatch(logout()),
|
||||
dispatchConnect: () => {
|
||||
dispatch(connectWS(config.WS_URL));
|
||||
},
|
||||
dispatchDisconnect: () => dispatch(disconnectWS()),
|
||||
dispatchSend: (message) => dispatch(send(message)),
|
||||
dispatchGetDashboardInformation: (dashboard, success, error) =>
|
||||
dispatch(getDashboardInformation(dashboard, success, error)),
|
||||
});
|
||||
@@ -162,10 +211,14 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
App.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
dispatchLogout: PropTypes.func.isRequired,
|
||||
dispatchConnect: PropTypes.func.isRequired,
|
||||
dispatchDisconnect: PropTypes.func.isRequired,
|
||||
dispatchSend: PropTypes.func.isRequired,
|
||||
// eslint-disable-next-line react/forbid-prop-types
|
||||
children: PropTypes.array.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
dashboard: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
connected: PropTypes.bool.isRequired,
|
||||
dashboard: PropTypes.object.isRequired,
|
||||
dispatchGetDashboardInformation: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
doGetKerberosAgentTags,
|
||||
doGetDashboardInformation,
|
||||
doGetEvents,
|
||||
doVerifyCamera,
|
||||
} from '../api/agent';
|
||||
|
||||
export const addRegion = (id, polygon) => {
|
||||
@@ -38,6 +39,29 @@ export const updateRegion = (id, polygon) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const verifyCamera = (streamType, config, onSuccess, onError) => {
|
||||
return (dispatch) => {
|
||||
doVerifyCamera(
|
||||
streamType,
|
||||
config,
|
||||
() => {
|
||||
dispatch({
|
||||
type: 'VERIFY_CAMERA',
|
||||
});
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
const { message } = error.response.data;
|
||||
if (onError) {
|
||||
onError(message);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export const verifyPersistence = (config, onSuccess, onError) => {
|
||||
return (dispatch) => {
|
||||
doVerifyPersistence(
|
||||
|
||||
@@ -91,6 +91,33 @@ export function doVerifyHub(config, onSuccess, onError) {
|
||||
});
|
||||
}
|
||||
|
||||
export function doVerifyCamera(streamType, config, onSuccess, onError) {
|
||||
const cameraStreams = {
|
||||
rtsp: '',
|
||||
sub_rtsp: '',
|
||||
};
|
||||
|
||||
if (config) {
|
||||
cameraStreams.rtsp = config.capture.ipcamera.rtsp;
|
||||
cameraStreams.sub_rtsp = config.capture.ipcamera.sub_rtsp;
|
||||
}
|
||||
|
||||
const endpoint = API.post(`camera/verify/${streamType}`, cameraStreams);
|
||||
endpoint
|
||||
.then((res) => {
|
||||
if (res.status !== 200) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
return res.message;
|
||||
})
|
||||
.then((data) => {
|
||||
onSuccess(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
onError(error);
|
||||
});
|
||||
}
|
||||
|
||||
export function doGetDashboardInformation(onSuccess, onError) {
|
||||
const endpoint = API.get(`dashboard`);
|
||||
endpoint
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import './LanguageSelect.scss';
|
||||
|
||||
const LanguageSelect = () => {
|
||||
let selected = localStorage.getItem('i18nextLng') || 'en';
|
||||
let selected = localStorage.getItem('i18nextLng') || i18next.language || 'en';
|
||||
const languageMap = {
|
||||
en: {
|
||||
label: 'English',
|
||||
@@ -20,6 +20,8 @@ const LanguageSelect = () => {
|
||||
fr: { label: 'Francais', dir: 'ltr', active: false },
|
||||
pl: { label: 'Polski', dir: 'ltr', active: false },
|
||||
de: { label: 'Deutsch', dir: 'ltr', active: false },
|
||||
pt: { label: 'Português', dir: 'ltr', active: false },
|
||||
es: { label: 'Español', dir: 'ltr', active: false },
|
||||
};
|
||||
|
||||
if (!languageMap[selected]) {
|
||||
@@ -74,6 +76,16 @@ const LanguageSelect = () => {
|
||||
{languageMap[item] ? languageMap[item].label : ''}
|
||||
</ListItem>
|
||||
))}
|
||||
<hr />
|
||||
<a
|
||||
href="https://github.com/kerberos-io/agent/issues/47"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<ListItem button key="contribute-language">
|
||||
Contribute language
|
||||
</ListItem>
|
||||
</a>
|
||||
</List>
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
@@ -21,4 +21,13 @@
|
||||
|
||||
#language-picker {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a, a:active, a:focus, a:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 8px 0 0 0 !important;
|
||||
padding: 4px 0 0 0 !important;
|
||||
}
|
||||
@@ -1,10 +1,20 @@
|
||||
const { hostname, host, protocol } = window.location;
|
||||
const websocketprotocol = protocol === 'http:' ? 'ws:' : 'wss:';
|
||||
|
||||
// Uncomment this when using codespaces or other special DNS names (which you can't control)
|
||||
// replace this with the DNS name of the kerberos agent server (the codespace url)
|
||||
// const externalHost = 'xxx-8080.preview.app.github.dev';
|
||||
|
||||
const dev = {
|
||||
ENV: 'dev',
|
||||
HOSTNAME: hostname,
|
||||
API_URL: `${protocol}//${hostname}:8080/api`,
|
||||
URL: `${protocol}//${hostname}:8080`,
|
||||
WS_URL: `${websocketprotocol}//${hostname}:8080/ws`,
|
||||
|
||||
// Uncomment, and comment the above lines, when using codespaces or other special DNS names (which you can't control)
|
||||
// API_URL: `${protocol}//${externalHost}/api`,
|
||||
// URL: `${protocol}//${externalHost}`,
|
||||
};
|
||||
|
||||
const prod = {
|
||||
@@ -12,6 +22,7 @@ const prod = {
|
||||
HOSTNAME: hostname,
|
||||
API_URL: `${protocol}//${host}/api`,
|
||||
URL: `${protocol}//${host}`,
|
||||
WS_URL: `${websocketprotocol}//${host}/ws`,
|
||||
};
|
||||
|
||||
const config = process.env.REACT_APP_ENVIRONMENT === 'production' ? prod : dev;
|
||||
|
||||
@@ -14,7 +14,7 @@ i18n
|
||||
escapeValue: false,
|
||||
},
|
||||
load: 'languageOnly',
|
||||
whitelist: ['de', 'en', 'nl', 'fr', 'pl'],
|
||||
whitelist: ['de', 'en', 'nl', 'fr', 'pl', 'es', 'pt'],
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { createStore, applyMiddleware } from 'redux';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { routerMiddleware, ConnectedRouter } from 'connected-react-router';
|
||||
import { Provider } from 'react-redux';
|
||||
import reduxWebsocket from '@giantmachines/redux-websocket';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||
import thunk from 'redux-thunk';
|
||||
import { Redirect } from 'react-router';
|
||||
@@ -49,10 +50,14 @@ function getAuthState() {
|
||||
}
|
||||
}
|
||||
|
||||
const reduxWebsocketMiddleware = reduxWebsocket();
|
||||
|
||||
const store = createStore(
|
||||
rootReducer(history),
|
||||
{ ...getAuthState() },
|
||||
composeWithDevTools(applyMiddleware(thunk, routerMiddleware(history)))
|
||||
composeWithDevTools(
|
||||
applyMiddleware(thunk, reduxWebsocketMiddleware, routerMiddleware(history))
|
||||
)
|
||||
);
|
||||
|
||||
const Loader = () => <div>loading...</div>;
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { send } from '@giantmachines/redux-websocket';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
Breadcrumb,
|
||||
@@ -45,6 +46,27 @@ class Dashboard extends React.Component {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const { connected } = this.props;
|
||||
if (connected === true) {
|
||||
const { dispatchSend } = this.props;
|
||||
const message = {
|
||||
message_type: 'stream-sd',
|
||||
};
|
||||
dispatchSend(message);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { connected: connectedPrev } = prevProps;
|
||||
const { connected } = this.props;
|
||||
if (connectedPrev === false && connected === true) {
|
||||
const { dispatchSend } = this.props;
|
||||
const message = {
|
||||
message_type: 'stream-sd',
|
||||
};
|
||||
dispatchSend(message);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -52,6 +74,12 @@ class Dashboard extends React.Component {
|
||||
if (liveview && liveview.length > 0) {
|
||||
liveview[0].remove();
|
||||
}
|
||||
|
||||
const { dispatchSend } = this.props;
|
||||
const message = {
|
||||
message_type: 'stop-sd',
|
||||
};
|
||||
dispatchSend(message);
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
@@ -73,7 +101,7 @@ class Dashboard extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dashboard, t } = this.props;
|
||||
const { dashboard, t, images } = this.props;
|
||||
const { liveviewLoaded, open, currentRecording } = this.state;
|
||||
|
||||
// We check if the camera was getting a valid frame
|
||||
@@ -273,7 +301,12 @@ class Dashboard extends React.Component {
|
||||
/>
|
||||
)}
|
||||
<div style={{ visibility: liveviewLoaded ? 'visible' : 'hidden' }}>
|
||||
<ImageCard imageSrc={`${config.API_URL}/stream?token=xxxx`} />
|
||||
<ImageCard
|
||||
imageSrc={`data:image/png;base64, ${
|
||||
images.length ? images[0] : ''
|
||||
}`}
|
||||
onerror=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -285,13 +318,20 @@ class Dashboard extends React.Component {
|
||||
|
||||
const mapStateToProps = (state /* , ownProps */) => ({
|
||||
dashboard: state.agent.dashboard,
|
||||
connected: state.wss.connected,
|
||||
images: state.wss.images,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (/* dispatch , ownProps */) => ({});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
dispatchSend: (message) => dispatch(send(message)),
|
||||
});
|
||||
|
||||
Dashboard.propTypes = {
|
||||
dashboard: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
dashboard: PropTypes.object.isRequired,
|
||||
connected: PropTypes.bool.isRequired,
|
||||
images: PropTypes.array.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
dispatchSend: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withTranslation()(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import {
|
||||
Breadcrumb,
|
||||
VideoContainer,
|
||||
@@ -86,17 +87,21 @@ class Media extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { events, eventsLoaded } = this.props;
|
||||
const { events, eventsLoaded, t } = this.props;
|
||||
const { isScrolling, open, currentRecording } = this.state;
|
||||
return (
|
||||
<div id="media">
|
||||
<Breadcrumb
|
||||
title="Recordings"
|
||||
level1="All your recordings in a single place"
|
||||
title={t('recordings.title')}
|
||||
level1={t('recordings.heading')}
|
||||
level1Link=""
|
||||
>
|
||||
<Link to="/settings">
|
||||
<Button label="Configure" icon="preferences" type="default" />
|
||||
<Button
|
||||
label={t('breadcrumb.configure')}
|
||||
icon="preferences"
|
||||
type="default"
|
||||
/>
|
||||
</Link>
|
||||
</Breadcrumb>
|
||||
|
||||
@@ -104,7 +109,7 @@ class Media extends React.Component {
|
||||
<Input
|
||||
iconleft="search"
|
||||
onChange={() => {}}
|
||||
placeholder="Search media..."
|
||||
placeholder={t('recordings.search_media')}
|
||||
layout="controlbar"
|
||||
type="text"
|
||||
/>
|
||||
@@ -195,9 +200,12 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
|
||||
Media.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
events: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
eventsLoaded: PropTypes.number.isRequired,
|
||||
dispatchGetEvents: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Media));
|
||||
export default withTranslation()(
|
||||
withRouter(connect(mapStateToProps, mapDispatchToProps)(Media))
|
||||
);
|
||||
|
||||
@@ -22,24 +22,18 @@
|
||||
|
||||
video {width: 100%;}
|
||||
|
||||
.lds-ripple {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-ripple div {
|
||||
position: absolute;
|
||||
border: 4px solid #fff;
|
||||
opacity: 1;
|
||||
border-radius: 50%;
|
||||
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||
animation: lds-ripple2 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||
}
|
||||
.lds-ripple div:nth-child(2) {
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
@keyframes lds-ripple {
|
||||
@keyframes lds-ripple2 {
|
||||
0% {
|
||||
top: 36px;
|
||||
left: 36px;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,7 @@
|
||||
.message {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.warning-message {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
@@ -64,6 +63,10 @@
|
||||
.block-footer .button:first-child {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.block-footer .button:nth-child(2) {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.toggle-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -2,10 +2,12 @@ import { combineReducers } from 'redux';
|
||||
import { connectRouter } from 'connected-react-router';
|
||||
import authentication from './authentication';
|
||||
import agent from './agent';
|
||||
import wss from './wss';
|
||||
|
||||
export default (history) =>
|
||||
combineReducers({
|
||||
authentication,
|
||||
agent,
|
||||
wss,
|
||||
router: connectRouter(history),
|
||||
});
|
||||
|
||||
79
ui/src/reducers/wss.js
Normal file
79
ui/src/reducers/wss.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
WEBSOCKET_BROKEN,
|
||||
WEBSOCKET_CLOSED,
|
||||
WEBSOCKET_CONNECT,
|
||||
WEBSOCKET_DISCONNECT,
|
||||
WEBSOCKET_MESSAGE,
|
||||
WEBSOCKET_OPEN,
|
||||
WEBSOCKET_SEND,
|
||||
} from '@giantmachines/redux-websocket';
|
||||
|
||||
export const WEBSOCKET_PREFIX = 'REDUX_WEBSOCKET';
|
||||
export const REDUX_WEBSOCKET_BROKEN = `${WEBSOCKET_PREFIX}::${WEBSOCKET_BROKEN}`;
|
||||
export const REDUX_WEBSOCKET_OPEN = `${WEBSOCKET_PREFIX}::${WEBSOCKET_OPEN}`;
|
||||
export const REDUX_WEBSOCKET_CLOSED = `${WEBSOCKET_PREFIX}::${WEBSOCKET_CLOSED}`;
|
||||
export const REDUX_WEBSOCKET_MESSAGE = `${WEBSOCKET_PREFIX}::${WEBSOCKET_MESSAGE}`;
|
||||
export const REDUX_WEBSOCKET_CONNECT = `${WEBSOCKET_PREFIX}::${WEBSOCKET_CONNECT}`;
|
||||
export const REDUX_WEBSOCKET_DISCONNECT = `${WEBSOCKET_PREFIX}::${WEBSOCKET_DISCONNECT}`;
|
||||
export const REDUX_WEBSOCKET_SEND = `${WEBSOCKET_PREFIX}::${WEBSOCKET_SEND}`;
|
||||
|
||||
const wss = (
|
||||
state = {
|
||||
connected: false,
|
||||
messages: {},
|
||||
events: [],
|
||||
images: [],
|
||||
url: null,
|
||||
},
|
||||
action
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case 'INTERNAL::CLEAR_MESSAGE_LOG':
|
||||
return {
|
||||
...state,
|
||||
messages: [],
|
||||
};
|
||||
|
||||
case REDUX_WEBSOCKET_CONNECT:
|
||||
return {
|
||||
...state,
|
||||
url: action.payload.url,
|
||||
};
|
||||
|
||||
case REDUX_WEBSOCKET_OPEN:
|
||||
return {
|
||||
...state,
|
||||
connected: true,
|
||||
};
|
||||
|
||||
case REDUX_WEBSOCKET_BROKEN:
|
||||
case REDUX_WEBSOCKET_CLOSED:
|
||||
return {
|
||||
...state,
|
||||
connected: false,
|
||||
};
|
||||
|
||||
case REDUX_WEBSOCKET_MESSAGE:
|
||||
const { payload } = action;
|
||||
const m = JSON.parse(payload.message);
|
||||
if (m) {
|
||||
const { message_type, message } = m;
|
||||
switch (message_type) {
|
||||
case 'image':
|
||||
const { base64 } = message;
|
||||
return {
|
||||
...state,
|
||||
images: [base64],
|
||||
};
|
||||
|
||||
default: // Anything else should be logging (to be refactored).
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export default wss;
|
||||
119
ui/yarn.lock
119
ui/yarn.lock
@@ -1049,6 +1049,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.14.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.19.0":
|
||||
version "7.20.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd"
|
||||
integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@babel/template@^7.18.10", "@babel/template@^7.18.6", "@babel/template@^7.3.3":
|
||||
version "7.18.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71"
|
||||
@@ -1561,10 +1568,10 @@
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@kerberos-io/ui@1.66.0":
|
||||
version "1.66.0"
|
||||
resolved "https://registry.yarnpkg.com/@kerberos-io/ui/-/ui-1.66.0.tgz#65ea64359d14be440bc9b142f33ac3151e9a39ee"
|
||||
integrity sha512-sbRgrU0C9DH6yvaqqKEg6MArp5SaL3YKVcxpOtS+PfRcAOXgn0B+4Py68DaWe5jNsZaVvXYJqnpR4V6wqj8rZg==
|
||||
"@kerberos-io/ui@1.70.0":
|
||||
version "1.70.0"
|
||||
resolved "https://registry.yarnpkg.com/@kerberos-io/ui/-/ui-1.70.0.tgz#377f42725cc98d02af43e133cf66f3c10852caf0"
|
||||
integrity sha512-Sq7gxNAXUFtjUNUpKBKogbvnJ03l/oxkf7943I0GpAzI0n+YdWNxlGpShB5HxsiarRsb+HphwZjAZZY+WupUwA==
|
||||
dependencies:
|
||||
"@svgr/webpack" "^5.5.0"
|
||||
"@types/react-router-dom" "^5.1.7"
|
||||
@@ -1604,6 +1611,13 @@
|
||||
react-is "^16.8.0 || ^17.0.0"
|
||||
react-transition-group "^4.4.0"
|
||||
|
||||
"@material-ui/icons@^4.11.3":
|
||||
version "4.11.3"
|
||||
resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.11.3.tgz#b0693709f9b161ce9ccde276a770d968484ecff1"
|
||||
integrity sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.4"
|
||||
|
||||
"@material-ui/styles@^4.11.5":
|
||||
version "4.11.5"
|
||||
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.5.tgz#19f84457df3aafd956ac863dbe156b1d88e2bbfb"
|
||||
@@ -3457,6 +3471,13 @@ cosmiconfig@^7.0.0:
|
||||
path-type "^4.0.0"
|
||||
yaml "^1.10.0"
|
||||
|
||||
cross-fetch@3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
|
||||
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
|
||||
dependencies:
|
||||
node-fetch "2.6.7"
|
||||
|
||||
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
@@ -5163,6 +5184,13 @@ html-minifier-terser@^6.0.2:
|
||||
relateurl "^0.2.7"
|
||||
terser "^5.10.0"
|
||||
|
||||
html-parse-stringify@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
|
||||
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
|
||||
dependencies:
|
||||
void-elements "3.1.0"
|
||||
|
||||
html-webpack-plugin@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50"
|
||||
@@ -5267,6 +5295,34 @@ hyphenate-style-name@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
|
||||
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
|
||||
|
||||
i18next-browser-languagedetector@^6.1.5:
|
||||
version "6.1.8"
|
||||
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.8.tgz#8e9c61b32a4dfe9b959b38bc9d2a8b95f799b27c"
|
||||
integrity sha512-Svm+MduCElO0Meqpj1kJAriTC6OhI41VhlT/A0UPjGoPZBhAHIaGE5EfsHlTpgdH09UVX7rcc72pSDDBeKSQQA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.19.0"
|
||||
|
||||
i18next-http-backend@^1.4.1:
|
||||
version "1.4.5"
|
||||
resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-1.4.5.tgz#4803eb071eb6ffb147c36bdd42ba6f64b3198ac2"
|
||||
integrity sha512-tLuHWuLWl6CmS07o+UB6EcQCaUjrZ1yhdseIN7sfq0u7phsMePJ8pqlGhIAdRDPF/q7ooyo5MID5DRFBCH+x5w==
|
||||
dependencies:
|
||||
cross-fetch "3.1.5"
|
||||
|
||||
i18next-xhr-backend@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/i18next-xhr-backend/-/i18next-xhr-backend-3.2.2.tgz#769124441461b085291f539d91864e3691199178"
|
||||
integrity sha512-OtRf2Vo3IqAxsttQbpjYnmMML12IMB5e0fc5B7qKJFLScitYaXa1OhMX0n0X/3vrfFlpHL9Ro/H+ps4Ej2j7QQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
|
||||
i18next@^21.9.1:
|
||||
version "21.10.0"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.10.0.tgz#85429af55fdca4858345d0e16b584ec29520197d"
|
||||
integrity sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.17.2"
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
@@ -6750,6 +6806,13 @@ no-case@^3.0.4:
|
||||
lower-case "^2.0.2"
|
||||
tslib "^2.0.3"
|
||||
|
||||
node-fetch@2.6.7:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-forge@^1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
|
||||
@@ -7920,6 +7983,14 @@ react-error-overlay@^6.0.11:
|
||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
|
||||
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
|
||||
|
||||
react-i18next@^11.18.5:
|
||||
version "11.18.6"
|
||||
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.18.6.tgz#e159c2960c718c1314f1e8fcaa282d1c8b167887"
|
||||
integrity sha512-yHb2F9BiT0lqoQDt8loZ5gWP331GwctHz9tYQ8A2EIEUu+CcEdjBLQWli1USG3RdWQt3W+jqQLg/d4rrQR96LA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.14.5"
|
||||
html-parse-stringify "^3.0.1"
|
||||
|
||||
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
@@ -8155,6 +8226,11 @@ regenerate@^1.4.2:
|
||||
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
|
||||
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
|
||||
|
||||
regenerator-runtime@^0.13.11:
|
||||
version "0.13.11"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
|
||||
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
|
||||
|
||||
regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.9:
|
||||
version "0.13.9"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
||||
@@ -8369,6 +8445,13 @@ rx@4.1.0:
|
||||
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
|
||||
integrity sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==
|
||||
|
||||
rxjs@^7.5.6:
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4"
|
||||
integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
@@ -9184,6 +9267,11 @@ tr46@^2.1.0:
|
||||
dependencies:
|
||||
punycode "^2.1.1"
|
||||
|
||||
tr46@~0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
||||
|
||||
traverse@0.6.6:
|
||||
version "0.6.6"
|
||||
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137"
|
||||
@@ -9214,6 +9302,11 @@ tslib@^2.0.3:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
||||
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
||||
|
||||
tslib@^2.1.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
|
||||
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
|
||||
|
||||
tsutils@^3.21.0:
|
||||
version "3.21.0"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
|
||||
@@ -9427,6 +9520,11 @@ vary@~1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
||||
|
||||
void-elements@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
|
||||
|
||||
vott-ct@^2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/vott-ct/-/vott-ct-2.3.3.tgz#bee5c5aeee16c3c89508c1632cf23009dad3d910"
|
||||
@@ -9475,6 +9573,11 @@ web-vitals@^0.2.4:
|
||||
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-0.2.4.tgz#ec3df43c834a207fd7cdefd732b2987896e08511"
|
||||
integrity sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
|
||||
|
||||
webidl-conversions@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
||||
@@ -9626,6 +9729,14 @@ whatwg-mimetype@^2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
|
||||
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
|
||||
|
||||
whatwg-url@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
||||
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
|
||||
dependencies:
|
||||
tr46 "~0.0.3"
|
||||
webidl-conversions "^3.0.0"
|
||||
|
||||
whatwg-url@^7.0.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
|
||||
|
||||
Reference in New Issue
Block a user