Compare commits

...

20 Commits

Author SHA1 Message Date
Cédric Verstraeten
6aed20c466 Align to correct region 2023-06-22 15:53:46 +02:00
Cedric Verstraeten
6672535544 fix glitch with loading livestream when hitting dashboard page first 2023-06-14 17:14:40 +02:00
Cedric Verstraeten
ed397b6ecc change environment box sizes 2023-06-14 16:59:26 +02:00
Cedric Verstraeten
530e4c654e set permissions to modify the env.js file on runtime 2023-06-14 16:49:50 +02:00
Cedric Verstraeten
913bd1ba12 add demo environment mode 2023-06-14 16:29:13 +02:00
Cedric Verstraeten
84e532be47 reloading of configuration right after updating config + optimise loading stream + fix for ip camera online and cloud online + onvif ptz checks for hub 2023-06-13 23:12:58 +02:00
Cedric Verstraeten
3341e99af1 timetable might be empty 2023-06-13 09:22:51 +02:00
Cedric Verstraeten
ced6e678ec Update Settings.jsx 2023-06-12 21:08:05 +02:00
Cedric Verstraeten
340a5d7ef6 Update Settings.jsx 2023-06-12 21:06:21 +02:00
Cedric Verstraeten
60e8edc876 add onvif verify option + improve streaming logic + reconnect websocket 2023-06-12 20:33:41 +02:00
Cedric Verstraeten
9cf9babd73 introduce camera connected variable + allow MQTT stream to be connected even when no camera attached 2023-06-09 14:44:09 +02:00
Cedric Verstraeten
229c246e1c adding ctx (support) + unblock when unsupported codec 2023-06-08 21:50:10 +02:00
Cedric Verstraeten
15d9bcda4f decoding issue caused new mongodb adapter to fail 2023-06-07 22:01:44 +02:00
Cedric Verstraeten
068063695e time table might be empty 2023-06-07 20:24:09 +02:00
Cedric Verstraeten
b1722844f3 fix config overriding 2023-06-07 20:19:50 +02:00
Cedric Verstraeten
eb5ab48d6c timetable might be empty 2023-06-07 18:47:48 +02:00
Cedric Verstraeten
b64f1039d7 hotfix: fix missing polygon from start 2023-06-07 18:22:18 +02:00
Cedric Verstraeten
6fcd6e53a1 hotfix: fix openconfig 2023-06-07 17:07:39 +02:00
Cedric Verstraeten
25537b5f02 add new mongodb adapter 2023-06-07 16:44:54 +02:00
Cédric Verstraeten
2fad541e06 Update README.md 2023-06-03 08:49:58 +02:00
48 changed files with 1241 additions and 461 deletions

View File

@@ -122,6 +122,7 @@ 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
RUN chown -R agent:kerberosio /home/agent/www
###########################
# Grant the necessary root capabilities to the process trying to bind to the privileged port

View File

@@ -28,8 +28,8 @@ Kerberos Agent is an isolated and scalable video (surveillance) management agent
## :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.
- (or) a USB camera, Raspberry Pi camera or other camera, that [you can tranform to a valid RTSP H264 stream](https://github.com/kerberos-io/camera-to-rtsp).
- Any hardware (ARMv6, ARMv7, ARM64, AMD) that can run a binary or 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?
@@ -165,6 +165,7 @@ Next to attaching the configuration file, it is also possible to override the co
| Name | Description | Default Value |
| --------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------ |
| `AGENT_MODE` | You can choose to run this in 'release' for production, and or 'demo' for showcasing. | "release" |
| `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. | "" |

View File

@@ -95,7 +95,7 @@
"s3": {
"proxyuri": "http://proxy.kerberos.io",
"bucket": "kerberosaccept",
"region": "eu-west1"
"region": "eu-west-1"
},
"kstorage": {},
"dropbox": {},
@@ -112,4 +112,4 @@
"hub_private_key": "",
"hub_site": "",
"condition_uri": ""
}
}

View File

@@ -244,6 +244,40 @@ const docTemplate = `{
}
}
},
"/api/onvif/verify": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Will verify the ONVIF connectivity.",
"tags": [
"config"
],
"summary": "Will verify the ONVIF connectivity.",
"operationId": "verify-onvif",
"parameters": [
{
"description": "Camera Config",
"name": "cameraConfig",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.IPCamera"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.APIResponse"
}
}
}
}
},
"/api/persistence/verify": {
"post": {
"security": [
@@ -347,9 +381,15 @@ const docTemplate = `{
"ipcamera": {
"$ref": "#/definitions/models.IPCamera"
},
"liveview": {
"type": "string"
},
"maxlengthrecording": {
"type": "integer"
},
"motion": {
"type": "string"
},
"name": {
"type": "string"
},
@@ -365,6 +405,12 @@ const docTemplate = `{
"raspicamera": {
"$ref": "#/definitions/models.RaspiCamera"
},
"recording": {
"type": "string"
},
"snapshots": {
"type": "string"
},
"transcodingresolution": {
"type": "integer"
},
@@ -391,6 +437,12 @@ const docTemplate = `{
"condition_uri": {
"type": "string"
},
"dropbox": {
"$ref": "#/definitions/models.Dropbox"
},
"friendly_name": {
"type": "string"
},
"heartbeaturi": {
"description": "obsolete",
"type": "string"
@@ -434,6 +486,9 @@ const docTemplate = `{
"region": {
"$ref": "#/definitions/models.Region"
},
"remove_after_upload": {
"type": "string"
},
"s3": {
"$ref": "#/definitions/models.S3"
},
@@ -477,6 +532,17 @@ const docTemplate = `{
}
}
},
"models.Dropbox": {
"type": "object",
"properties": {
"access_token": {
"type": "string"
},
"directory": {
"type": "string"
}
}
},
"models.IPCamera": {
"type": "object",
"properties": {
@@ -484,7 +550,7 @@ const docTemplate = `{
"type": "string"
},
"onvif": {
"type": "boolean"
"type": "string"
},
"onvif_password": {
"type": "string"

View File

@@ -236,6 +236,40 @@
}
}
},
"/api/onvif/verify": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Will verify the ONVIF connectivity.",
"tags": [
"config"
],
"summary": "Will verify the ONVIF connectivity.",
"operationId": "verify-onvif",
"parameters": [
{
"description": "Camera Config",
"name": "cameraConfig",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.IPCamera"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.APIResponse"
}
}
}
}
},
"/api/persistence/verify": {
"post": {
"security": [
@@ -339,9 +373,15 @@
"ipcamera": {
"$ref": "#/definitions/models.IPCamera"
},
"liveview": {
"type": "string"
},
"maxlengthrecording": {
"type": "integer"
},
"motion": {
"type": "string"
},
"name": {
"type": "string"
},
@@ -357,6 +397,12 @@
"raspicamera": {
"$ref": "#/definitions/models.RaspiCamera"
},
"recording": {
"type": "string"
},
"snapshots": {
"type": "string"
},
"transcodingresolution": {
"type": "integer"
},
@@ -383,6 +429,12 @@
"condition_uri": {
"type": "string"
},
"dropbox": {
"$ref": "#/definitions/models.Dropbox"
},
"friendly_name": {
"type": "string"
},
"heartbeaturi": {
"description": "obsolete",
"type": "string"
@@ -426,6 +478,9 @@
"region": {
"$ref": "#/definitions/models.Region"
},
"remove_after_upload": {
"type": "string"
},
"s3": {
"$ref": "#/definitions/models.S3"
},
@@ -469,6 +524,17 @@
}
}
},
"models.Dropbox": {
"type": "object",
"properties": {
"access_token": {
"type": "string"
},
"directory": {
"type": "string"
}
}
},
"models.IPCamera": {
"type": "object",
"properties": {
@@ -476,7 +542,7 @@
"type": "string"
},
"onvif": {
"type": "boolean"
"type": "string"
},
"onvif_password": {
"type": "string"

View File

@@ -44,8 +44,12 @@ definitions:
type: integer
ipcamera:
$ref: '#/definitions/models.IPCamera'
liveview:
type: string
maxlengthrecording:
type: integer
motion:
type: string
name:
type: string
pixelChangeThreshold:
@@ -56,6 +60,10 @@ definitions:
type: integer
raspicamera:
$ref: '#/definitions/models.RaspiCamera'
recording:
type: string
snapshots:
type: string
transcodingresolution:
type: integer
transcodingwebrtc:
@@ -73,6 +81,10 @@ definitions:
type: string
condition_uri:
type: string
dropbox:
$ref: '#/definitions/models.Dropbox'
friendly_name:
type: string
heartbeaturi:
description: obsolete
type: string
@@ -102,6 +114,8 @@ definitions:
type: string
region:
$ref: '#/definitions/models.Region'
remove_after_upload:
type: string
s3:
$ref: '#/definitions/models.S3'
stunuri:
@@ -130,12 +144,19 @@ definitions:
"y":
type: number
type: object
models.Dropbox:
properties:
access_token:
type: string
directory:
type: string
type: object
models.IPCamera:
properties:
fps:
type: string
onvif:
type: boolean
type: string
onvif_password:
type: string
onvif_username:
@@ -414,6 +435,27 @@ paths:
summary: Get Authorization token.
tags:
- authentication
/api/onvif/verify:
post:
description: Will verify the ONVIF connectivity.
operationId: verify-onvif
parameters:
- description: Camera Config
in: body
name: cameraConfig
required: true
schema:
$ref: '#/definitions/models.IPCamera'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.APIResponse'
security:
- Bearer: []
summary: Will verify the ONVIF connectivity.
tags:
- config
/api/persistence/verify:
post:
description: Will verify the persistence.

View File

@@ -2,9 +2,8 @@ module github.com/kerberos-io/agent/machinery
go 1.19
//replace github.com/kerberos-io/joy4 v1.0.54 => ../../../../github.com/kerberos-io/joy4
//replace github.com/kerberos-io/onvif v0.0.5 => ../../../../github.com/kerberos-io/onvif
// replace github.com/kerberos-io/joy4 v1.0.57 => ../../../../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
@@ -25,7 +24,7 @@ require (
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.57
github.com/kerberos-io/joy4 v1.0.58
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
@@ -37,8 +36,8 @@ require (
github.com/swaggo/gin-swagger v1.5.3
github.com/swaggo/swag v1.8.9
github.com/tevino/abool v1.2.0
go.mongodb.org/mongo-driver v1.7.5
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
)
@@ -71,16 +70,19 @@ require (
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.1 // indirect
github.com/go-stack/stack v1.8.0 // 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/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20210423192551-a2663126120b // indirect
github.com/google/uuid v1.3.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/compress v1.15.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
@@ -117,6 +119,10 @@ require (
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/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.0.2 // indirect
github.com/xdg-go/stringprep v1.0.2 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
github.com/ziutek/mymysql v1.5.4 // indirect
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect

View File

@@ -162,6 +162,8 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
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-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
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.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
@@ -201,6 +203,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -211,6 +215,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -259,11 +264,14 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
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.57 h1:/8epNAJv4cOzBG8pFiM9hVNXfwsgA+8/2nHQ2yOeyII=
github.com/kerberos-io/joy4 v1.0.57/go.mod h1:nZp4AjvKvTOXRrmDyAIOw+Da+JA5OcSo/JundGfOlFU=
github.com/kerberos-io/joy4 v1.0.58 h1:R8EECSF+bG7o2yHC6cX/lF77Z+bDVGl6OioLZ3+5MN4=
github.com/kerberos-io/joy4 v1.0.58/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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -305,6 +313,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nsmith5/mjpeg v0.0.0-20200913181537-54b8ada0e53e h1:bQo/jQ9qvcw7zqnovm8IbLsaOq3F+ELUQcxtxvalQvA=
github.com/nsmith5/mjpeg v0.0.0-20200913181537-54b8ada0e53e/go.mod h1:PW9xCZScEClMBP22n37i0SnN/8B9YzNXTNvOaIkLjv0=
@@ -437,6 +446,7 @@ 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.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
@@ -447,6 +457,14 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -455,6 +473,8 @@ github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.mongodb.org/mongo-driver v1.7.5 h1:ny3p0reEpgsR2cfA5cjgwFZg3Cv/ofFh/8jbhGtz9VI=
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -470,6 +490,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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=
@@ -649,6 +670,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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=
@@ -670,6 +692,7 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@@ -808,8 +831,6 @@ 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/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=

View File

@@ -1,6 +1,7 @@
package main
import (
"context"
"os"
"time"
@@ -16,7 +17,6 @@ import (
var VERSION = "3.0.0"
func main() {
// You might be interested in debugging the agent.
if os.Getenv("DATADOG_AGENT_ENABLED") == "true" {
if os.Getenv("DATADOG_AGENT_K8S_ENABLED") == "true" {
@@ -111,8 +111,14 @@ func main() {
}
}
// Create a cancelable context, which will be used to cancel and restart.
// This is used to restart the agent when the configuration is updated.
ctx, cancel := context.WithCancel(context.Background())
// Bootstrapping the agent
communication := models.Communication{
Context: &ctx,
CancelContext: &cancel,
HandleBootstrap: make(chan string, 1),
}
go components.Bootstrap(&configuration, &communication)

View File

@@ -1,6 +1,7 @@
package capture
import (
"context"
"strconv"
"sync"
"time"
@@ -15,9 +16,9 @@ import (
"github.com/kerberos-io/joy4/format"
)
func OpenRTSP(url string) (av.DemuxCloser, []av.CodecData, error) {
func OpenRTSP(ctx context.Context, url string) (av.DemuxCloser, []av.CodecData, error) {
format.RegisterAll()
infile, err := avutil.Open(url)
infile, err := avutil.Open(ctx, url)
if err == nil {
streams, errstreams := infile.Streams()
return infile, streams, errstreams

View File

@@ -2,6 +2,7 @@
package capture
import (
"context"
"os"
"strconv"
"time"
@@ -431,6 +432,10 @@ func VerifyCamera(c *gin.Context) {
var cameraStreams models.CameraStreams
err := c.BindJSON(&cameraStreams)
// Should return in 5 seconds.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err == nil {
streamType := c.Param("streamType")
@@ -442,7 +447,7 @@ func VerifyCamera(c *gin.Context) {
if streamType == "secondary" {
rtspUrl = cameraStreams.SubRTSP
}
_, codecs, err := OpenRTSP(rtspUrl)
_, codecs, err := OpenRTSP(ctx, rtspUrl)
if err == nil {
videoIdx := -1

View File

@@ -272,13 +272,21 @@ loop:
// We'll check which mode is enabled for the camera.
onvifEnabled := "false"
onvifZoom := "false"
onvifPanTilt := "false"
if config.Capture.IPCamera.ONVIFXAddr != "" {
device, err := onvif.ConnectToOnvifDevice(configuration)
cameraConfiguration := configuration.Config.Capture.IPCamera
device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration)
if err == nil {
capabilities := onvif.GetCapabilitiesFromDevice(device)
for _, v := range capabilities {
if v == "PTZ" || v == "ptz" {
onvifEnabled = "true"
configurations, err := onvif.GetPTZConfigurationsFromDevice(device)
if err == nil {
onvifEnabled = "true"
_, canZoom, canPanTilt := onvif.GetPTZFunctionsFromDevice(configurations)
if canZoom {
onvifZoom = "true"
}
if canPanTilt {
onvifPanTilt = "true"
}
}
}
@@ -324,6 +332,8 @@ loop:
"boot_time" : "%s",
"siteID" : "%s",
"onvif" : "%s",
"onvif_zoom" : "%s",
"onvif_pantilt" : "%s",
"cameraConnected": "%s",
"numberoffiles" : "33",
"timestamp" : 1564747908,
@@ -331,7 +341,7 @@ loop:
"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.ProcessUsedMemory, macs, ips, "0", "0", "0", uptimeString, boottimeString, config.HubSite, onvifEnabled, cameraConnected)
}`, config.Key, system.Version, system.CPUId, username, key, name, isEnterprise, system.Hostname, system.Architecture, system.TotalMemory, system.UsedMemory, system.FreeMemory, system.ProcessUsedMemory, macs, ips, "0", "0", "0", uptimeString, boottimeString, config.HubSite, onvifEnabled, onvifZoom, onvifPanTilt, cameraConnected)
var jsonStr = []byte(object)
buffy := bytes.NewBuffer(jsonStr)
@@ -347,6 +357,7 @@ loop:
communication.CloudTimestamp.Store(time.Now().Unix())
log.Log.Info("HandleHeartBeat: (200) Heartbeat received by Kerberos Hub.")
} else {
communication.CloudTimestamp.Store(0)
log.Log.Error("HandleHeartBeat: (400) Something went wrong while sending to Kerberos Hub.")
}

View File

@@ -1,6 +1,7 @@
package components
import (
"context"
"encoding/json"
"errors"
"image"
@@ -16,7 +17,7 @@ import (
"github.com/kerberos-io/agent/machinery/src/database"
"github.com/kerberos-io/agent/machinery/src/log"
"github.com/kerberos-io/agent/machinery/src/models"
"gopkg.in/mgo.v2/bson"
"go.mongodb.org/mongo-driver/bson"
)
func GetImageFromFilePath() (image.Image, error) {
@@ -76,19 +77,31 @@ func OpenConfig(configuration *models.Configuration) {
// Multiple agents have there configuration stored, and can benefit from
// the concept of a global concept.
session := database.New().Copy()
defer session.Close()
db := session.DB(database.DatabaseName)
collection := db.C("configuration")
// Write to mongodb
client := database.New()
collection.Find(bson.M{
db := client.Database(database.DatabaseName)
collection := db.Collection("configuration")
var globalConfig models.Config
err := collection.FindOne(context.Background(), bson.M{
"type": "global",
}).One(&configuration.GlobalConfig)
}).Decode(&globalConfig)
if err != nil {
log.Log.Error("Could not find global configuration, using default configuration.")
}
configuration.GlobalConfig = globalConfig
collection.Find(bson.M{
var customConfig models.Config
deploymentName := os.Getenv("DEPLOYMENT_NAME")
err = collection.FindOne(context.Background(), bson.M{
"type": "config",
"name": os.Getenv("DEPLOYMENT_NAME"),
}).One(&configuration.CustomConfig)
"name": deploymentName,
}).Decode(&customConfig)
if err != nil {
log.Log.Error("Could not find configuration for " + deploymentName + ", using global configuration.")
}
configuration.CustomConfig = customConfig
// We will merge both configs in a single config file.
// Read again from database but this store overwrite the same object.
@@ -204,8 +217,7 @@ func OverrideWithEnvironmentVariables(configuration *models.Configuration) {
/* ONVIF connnection settings */
case "AGENT_CAPTURE_IPCAMERA_ONVIF":
isEnabled := value == " true"
configuration.Config.Capture.IPCamera.ONVIF = isEnabled
configuration.Config.Capture.IPCamera.ONVIF = value
break
case "AGENT_CAPTURE_IPCAMERA_ONVIF_XADDR":
configuration.Config.Capture.IPCamera.ONVIFXAddr = value
@@ -435,9 +447,11 @@ func SaveConfig(config models.Config, configuration *models.Configuration, commu
return err
}
select {
case communication.HandleBootstrap <- "restart":
default:
if communication.CameraConnected {
select {
case communication.HandleBootstrap <- "restart":
default:
}
}
communication.IsConfiguring.UnSet()
@@ -452,15 +466,19 @@ func StoreConfig(config models.Config) error {
// Save into database
if os.Getenv("DEPLOYMENT") == "factory" || os.Getenv("MACHINERY_ENVIRONMENT") == "kubernetes" {
// Write to mongodb
session := database.New().Copy()
defer session.Close()
db := session.DB(database.DatabaseName)
collection := db.C("configuration")
client := database.New()
err := collection.Update(bson.M{
db := client.Database(database.DatabaseName)
collection := db.Collection("configuration")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err := collection.UpdateOne(ctx, bson.M{
"type": "config",
"name": os.Getenv("DEPLOYMENT_NAME"),
}, &config)
}, bson.M{"$set": config})
return err
// Save into file

View File

@@ -1,12 +1,14 @@
package components
import (
"context"
"runtime"
"strconv"
"sync"
"sync/atomic"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/kerberos-io/joy4/cgo/ffmpeg"
"github.com/kerberos-io/agent/machinery/src/capture"
@@ -53,6 +55,7 @@ func Bootstrap(configuration *models.Configuration, communication *models.Commun
communication.HandleLiveSD = make(chan int64, 1)
communication.HandleLiveHDKeepalive = make(chan string, 1)
communication.HandleLiveHDPeers = make(chan string, 1)
communication.HandleONVIF = make(chan models.OnvifAction, 1)
communication.IsConfiguring = abool.New()
// Before starting the agent, we have a control goroutine, that might
@@ -67,33 +70,69 @@ func Bootstrap(configuration *models.Configuration, communication *models.Commun
// Handle heartbeats
go cloud.HandleHeartBeat(configuration, communication, uptimeStart)
// We'll create a MQTT handler, which will be used to communicate with Kerberos Hub.
// Configure a MQTT client which helps for a bi-directional communication
mqttClient := routers.ConfigureMQTT(configuration, communication)
// Run the agent and fire up all the other
// goroutines which do image capture, motion detection, onvif, etc.
for {
// This will blocking until receiving a signal to be restarted, reconfigured, stopped, etc.
status := RunAgent(configuration, communication, uptimeStart, cameraSettings, decoder, subDecoder)
status := RunAgent(configuration, communication, mqttClient, uptimeStart, cameraSettings, decoder, subDecoder)
if status == "stop" {
break
}
// We will re open the configuration, might have changed :O!
OpenConfig(configuration)
// We will override the configuration with the environment variables
OverrideWithEnvironmentVariables(configuration)
if status == "not started" {
// We will re open the configuration, might have changed :O!
OpenConfig(configuration)
// We will override the configuration with the environment variables
OverrideWithEnvironmentVariables(configuration)
}
// Reset the MQTT client, might have provided new information, so we need to reconnect.
if routers.HasMQTTClientModified(configuration) {
routers.DisconnectMQTT(mqttClient, &configuration.Config)
mqttClient = routers.ConfigureMQTT(configuration, communication)
}
// We will create a new cancelable context, which will be used to cancel and restart.
// This is used to restart the agent when the configuration is updated.
ctx, cancel := context.WithCancel(context.Background())
communication.Context = &ctx
communication.CancelContext = &cancel
}
log.Log.Debug("Bootstrap: finished")
}
func RunAgent(configuration *models.Configuration, communication *models.Communication, uptimeStart time.Time, cameraSettings *models.Camera, decoder *ffmpeg.VideoDecoder, subDecoder *ffmpeg.VideoDecoder) string {
func RunAgent(configuration *models.Configuration, communication *models.Communication, mqttClient mqtt.Client, uptimeStart time.Time, cameraSettings *models.Camera, decoder *ffmpeg.VideoDecoder, subDecoder *ffmpeg.VideoDecoder) string {
log.Log.Debug("RunAgent: bootstrapping agent")
config := configuration.Config
status := "not started"
// Currently only support H264 encoded cameras, this will change.
// Establishing the camera connection
rtspUrl := config.Capture.IPCamera.RTSP
infile, streams, err := capture.OpenRTSP(rtspUrl)
infile, streams, err := capture.OpenRTSP(context.Background(), rtspUrl)
// We will initialise the camera settings object
// so we can check if the camera settings have changed, and we need
// to reload the decoders.
videoStream, _ := capture.GetVideoStream(streams)
if videoStream == nil {
log.Log.Error("RunAgent: no video stream found, might be the wrong codec (we only support H264 for the moment)")
time.Sleep(time.Second * 3)
return status
}
num, denum := videoStream.(av.VideoCodecData).Framerate()
width := videoStream.(av.VideoCodecData).Width()
height := videoStream.(av.VideoCodecData).Height()
var queue *pubsub.Queue
var subQueue *pubsub.Queue
@@ -101,11 +140,9 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
var decoderMutex sync.Mutex
var subDecoderMutex sync.Mutex
status := "not started"
if err == nil {
log.Log.Info("RunAgent: opened RTSP stream" + rtspUrl)
log.Log.Info("RunAgent: opened RTSP stream: " + rtspUrl)
// We might have a secondary rtsp url, so we might need to use that.
var subInfile av.DemuxCloser
@@ -113,23 +150,21 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
subStreamEnabled := false
subRtspUrl := config.Capture.IPCamera.SubRTSP
if subRtspUrl != "" && subRtspUrl != rtspUrl {
subInfile, subStreams, err = capture.OpenRTSP(subRtspUrl)
subInfile, subStreams, err = capture.OpenRTSP(context.Background(), subRtspUrl)
if err == nil {
log.Log.Info("RunAgent: opened RTSP sub stream " + subRtspUrl)
subStreamEnabled = true
}
videoStream, _ := capture.GetVideoStream(subStreams)
if videoStream == nil {
log.Log.Error("RunAgent: no video substream found, might be the wrong codec (we only support H264 for the moment)")
time.Sleep(time.Second * 3)
return status
}
}
// We will initialise the camera settings object
// so we can check if the camera settings have changed, and we need
// to reload the decoders.
videoStream, _ := capture.GetVideoStream(streams)
num, denum := videoStream.(av.VideoCodecData).Framerate()
width := videoStream.(av.VideoCodecData).Width()
height := videoStream.(av.VideoCodecData).Height()
if cameraSettings.RTSP != rtspUrl || cameraSettings.SubRTSP != subRtspUrl || cameraSettings.Width != width || cameraSettings.Height != height || cameraSettings.Num != num || cameraSettings.Denum != denum || cameraSettings.Codec != videoStream.(av.VideoCodecData).Type() {
if cameraSettings.Initialized {
decoder.Close()
if subStreamEnabled {
@@ -187,10 +222,6 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
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 the camera stream
go capture.HandleStream(infile, queue, communication)
@@ -237,11 +268,27 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
// Handle ONVIF actions
go onvif.HandleONVIFActions(configuration, communication)
// If we reach this point, we have a working RTSP connection.
communication.CameraConnected = true
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// This will go into a blocking state, once this channel is triggered
// the agent will cleanup and restart.
status = <-communication.HandleBootstrap
// If we reach this point, we are stopping the stream.
communication.CameraConnected = false
// Cancel the main context, this will stop all the other goroutines.
(*communication.CancelContext)()
// We will re open the configuration, might have changed :O!
OpenConfig(configuration)
// We will override the configuration with the environment variables
OverrideWithEnvironmentVariables(configuration)
// Here we are cleaning up everything!
if configuration.Config.Offline != "true" {
communication.HandleUpload <- "stop"
@@ -265,19 +312,9 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
subQueue = nil
communication.SubQueue = nil
}
close(communication.HandleONVIF)
communication.HandleONVIF = nil
close(communication.HandleLiveHDHandshake)
communication.HandleLiveHDHandshake = nil
close(communication.HandleMotion)
communication.HandleMotion = nil
// Disconnect MQTT
routers.DisconnectMQTT(mqttClient, &configuration.Config)
// Wait a few seconds to stop the decoder.
time.Sleep(time.Second * 3)
// 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)
@@ -303,29 +340,32 @@ func ControlAgent(communication *models.Communication) {
var previousPacket int64 = 0
var occurence = 0
for {
packetsR := packageCounter.Load().(int64)
if packetsR == previousPacket {
// If we are already reconfiguring,
// we dont need to check if the stream is blocking.
if !communication.IsConfiguring.IsSet() {
occurence = occurence + 1
// If camera is connected, we'll check if we are still receiving packets.
if communication.CameraConnected {
packetsR := packageCounter.Load().(int64)
if packetsR == previousPacket {
// If we are already reconfiguring,
// we dont need to check if the stream is blocking.
if !communication.IsConfiguring.IsSet() {
occurence = occurence + 1
}
} else {
occurence = 0
}
} else {
occurence = 0
log.Log.Info("ControlAgent: Number of packets read " + strconv.FormatInt(packetsR, 10))
// After 15 seconds without activity this is thrown..
if occurence == 3 {
log.Log.Info("Main: Restarting machinery.")
communication.HandleBootstrap <- "restart"
time.Sleep(2 * time.Second)
occurence = 0
}
previousPacket = packageCounter.Load().(int64)
}
log.Log.Info("ControlAgent: Number of packets read " + strconv.FormatInt(packetsR, 10))
// After 15 seconds without activity this is thrown..
if occurence == 3 {
log.Log.Info("Main: Restarting machinery.")
communication.HandleBootstrap <- "restart"
time.Sleep(2 * time.Second)
occurence = 0
}
previousPacket = packageCounter.Load().(int64)
time.Sleep(5 * time.Second)
}
}()

View File

@@ -10,15 +10,12 @@ import (
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
geo "github.com/kellydunn/golang-geo"
"github.com/kerberos-io/agent/machinery/src/capture"
"github.com/kerberos-io/agent/machinery/src/log"
"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/av/pubsub"
"github.com/kerberos-io/joy4/cgo/ffmpeg"
)
@@ -77,18 +74,21 @@ func ProcessMotion(motionCursor *pubsub.QueueCursor, configuration *models.Confi
// Calculate mask
var polyObjects []geo.Polygon
for _, polygon := range config.Region.Polygon {
coords := polygon.Coordinates
poly := geo.Polygon{}
for _, c := range coords {
x := c.X
y := c.Y
p := geo.NewPoint(x, y)
if !poly.Contains(p) {
poly.Add(p)
if config.Region != nil {
for _, polygon := range config.Region.Polygon {
coords := polygon.Coordinates
poly := geo.Polygon{}
for _, c := range coords {
x := c.X
y := c.Y
p := geo.NewPoint(x, y)
if !poly.Contains(p) {
poly.Add(p)
}
}
polyObjects = append(polyObjects, poly)
}
polyObjects = append(polyObjects, poly)
}
bounds := img.Bounds()
@@ -139,19 +139,21 @@ func ProcessMotion(motionCursor *pubsub.QueueCursor, configuration *models.Confi
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) {
if config.Timetable != nil && len(config.Timetable) > 0 {
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.Info("ProcessMotion: Time interval not valid, disabling motion detection.")
} else {
detectMotion = false
log.Log.Info("ProcessMotion: Time interval not valid, disabling motion detection.")
}
}
}
}

View File

@@ -1,46 +1,55 @@
package database
import (
"context"
"fmt"
"os"
"strings"
"sync"
"time"
"github.com/kerberos-io/agent/machinery/src/log"
"gopkg.in/mgo.v2"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type DB struct {
Session *mgo.Session
Client *mongo.Client
}
var _init_ctx sync.Once
var _instance *DB
var DatabaseName = "KerberosFactory"
func New() *mgo.Session {
func New() *mongo.Client {
host := os.Getenv("MONGODB_HOST")
database := os.Getenv("MONGODB_DATABASE_CREDENTIALS")
databaseCredentials := os.Getenv("MONGODB_DATABASE_CREDENTIALS")
replicaset := os.Getenv("MONGODB_REPLICASET")
username := os.Getenv("MONGODB_USERNAME")
password := os.Getenv("MONGODB_PASSWORD")
authentication := "SCRAM-SHA-256"
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_init_ctx.Do(func() {
_instance = new(DB)
mongoDBDialInfo := &mgo.DialInfo{
Addrs: strings.Split(host, ","),
Timeout: 3 * time.Second,
Database: database,
Username: username,
Password: password,
mongodbURI := fmt.Sprintf("mongodb://%s:%s@%s", username, password, host)
if replicaset != "" {
mongodbURI = fmt.Sprintf("%s/?replicaSet=%s", mongodbURI, replicaset)
}
session, err := mgo.DialWithInfo(mongoDBDialInfo)
client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongodbURI).SetAuth(options.Credential{
AuthMechanism: authentication,
AuthSource: databaseCredentials,
Username: username,
Password: password,
}))
if err != nil {
log.Log.Error(fmt.Sprintf("Failed to connect to database: %s", err.Error()))
fmt.Printf("Error setting up mongodb connection: %+v\n", err)
os.Exit(1)
}
_instance.Session = session
_instance.Client = client
})
return _instance.Session
return _instance.Client
}

View File

@@ -1,8 +1,11 @@
package models
type APIResponse struct {
Data interface{} `json:"data" bson:"data"`
Message interface{} `json:"message" bson:"message"`
Data interface{} `json:"data" bson:"data"`
Message interface{} `json:"message" bson:"message"`
PTZFunctions interface{} `json:"ptz_functions" bson:"ptz_functions"`
CanZoom bool `json:"can_zoom" bson:"can_zoom"`
CanPanTilt bool `json:"can_pan_tilt" bson:"can_pan_tilt"`
}
type OnvifCredentials struct {

View File

@@ -1,6 +1,7 @@
package models
import (
"context"
"sync"
"sync/atomic"
@@ -12,6 +13,8 @@ import (
// The communication struct that is managing
// all the communication between the different goroutines.
type Communication struct {
Context *context.Context
CancelContext *context.CancelFunc
PackageCounter *atomic.Value
LastPacketTimer *atomic.Value
CloudTimestamp *atomic.Value

View File

@@ -73,7 +73,7 @@ type IPCamera struct {
RTSP string `json:"rtsp"`
SubRTSP string `json:"sub_rtsp"`
FPS string `json:"fps"`
ONVIF bool `json:"onvif,omitempty" bson:"onvif"`
ONVIF string `json:"onvif,omitempty" bson:"onvif"`
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"`

View File

@@ -10,6 +10,7 @@ import (
"strings"
"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/onvif/media"
@@ -31,7 +32,8 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode
json.Unmarshal(b, &ptzAction)
// Connect to Onvif device
device, err := ConnectToOnvifDevice(configuration)
cameraConfiguration := configuration.Config.Capture.IPCamera
device, err := ConnectToOnvifDevice(&cameraConfiguration)
if err == nil {
// Get token from the first profile
@@ -39,7 +41,7 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode
if err == nil {
// Get the configurations from the device
configurations, err := GetConfigurationsFromDevice(device)
configurations, err := GetPTZConfigurationsFromDevice(device)
if err == nil {
@@ -100,16 +102,13 @@ func HandleONVIFActions(configuration *models.Configuration, communication *mode
log.Log.Debug("HandleONVIFActions: finished")
}
func ConnectToOnvifDevice(configuration *models.Configuration) (*onvif.Device, error) {
func ConnectToOnvifDevice(cameraConfiguration *models.IPCamera) (*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,
Xaddr: cameraConfiguration.ONVIFXAddr,
Username: cameraConfiguration.ONVIFUsername,
Password: cameraConfiguration.ONVIFPassword,
})
if err != nil {
@@ -154,11 +153,11 @@ func GetTokenFromProfile(device *onvif.Device, profileId int) (xsd.ReferenceToke
return profileToken, err
}
func GetConfigurationsFromDevice(device *onvif.Device) (ptz.GetConfigurationsResponse, error) {
func GetPTZConfigurationsFromDevice(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
// Get the PTZ configurations from the device
resp, err := device.CallMethod(ptz.GetConfigurations{})
if err == nil {
defer resp.Body.Close()
@@ -167,11 +166,11 @@ func GetConfigurationsFromDevice(device *onvif.Device) (ptz.GetConfigurationsRes
stringBody := string(b)
decodedXML, et, err := getXMLNode(stringBody, "GetConfigurationsResponse")
if err != nil {
log.Log.Error("GetConfigurationsFromDevice: " + err.Error())
log.Log.Error("GetPTZConfigurationsFromDevice: " + err.Error())
return configurations, err
} else {
if err := decodedXML.DecodeElement(&configurations, et); err != nil {
log.Log.Error("GetConfigurationsFromDevice: " + err.Error())
log.Log.Error("GetPTZConfigurationsFromDevice: " + err.Error())
return configurations, err
}
}
@@ -317,3 +316,89 @@ func getXMLNode(xmlBody string, nodeName string) (*xml.Decoder, *xml.StartElemen
}
return nil, nil, errors.New("error in NodeName - username and password might be wrong")
}
func GetPTZFunctionsFromDevice(configurations ptz.GetConfigurationsResponse) ([]string, bool, bool) {
var functions []string
canZoom := false
canPanTilt := false
if configurations.PTZConfiguration.DefaultAbsolutePantTiltPositionSpace != "" {
functions = append(functions, "AbsolutePanTiltMove")
canPanTilt = true
}
if configurations.PTZConfiguration.DefaultAbsoluteZoomPositionSpace != "" {
functions = append(functions, "AbsoluteZoomMove")
canZoom = true
}
if configurations.PTZConfiguration.DefaultRelativePanTiltTranslationSpace != "" {
functions = append(functions, "RelativePanTiltMove")
canPanTilt = true
}
if configurations.PTZConfiguration.DefaultRelativeZoomTranslationSpace != "" {
functions = append(functions, "RelativeZoomMove")
canZoom = true
}
if configurations.PTZConfiguration.DefaultContinuousPanTiltVelocitySpace != "" {
functions = append(functions, "ContinuousPanTiltMove")
canPanTilt = true
}
if configurations.PTZConfiguration.DefaultContinuousZoomVelocitySpace != "" {
functions = append(functions, "ContinuousZoomMove")
canZoom = true
}
if configurations.PTZConfiguration.DefaultPTZSpeed != nil {
functions = append(functions, "PTZSpeed")
}
if configurations.PTZConfiguration.DefaultPTZTimeout != "" {
functions = append(functions, "PTZTimeout")
}
return functions, canZoom, canPanTilt
}
// VerifyOnvifConnection godoc
// @Router /api/onvif/verify [post]
// @ID verify-onvif
// @Security Bearer
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @Tags config
// @Param cameraConfig body models.IPCamera true "Camera Config"
// @Summary Will verify the ONVIF connectivity.
// @Description Will verify the ONVIF connectivity.
// @Success 200 {object} models.APIResponse
func VerifyOnvifConnection(c *gin.Context) {
var cameraConfig models.IPCamera
err := c.BindJSON(&cameraConfig)
if err == nil {
device, err := ConnectToOnvifDevice(&cameraConfig)
if err == nil {
// Get the list of configurations
configurations, err := GetPTZConfigurationsFromDevice(device)
if err == nil {
// Check if can zoom and/or pan/tilt is supported
ptzFunctions, canZoom, canPanTilt := GetPTZFunctionsFromDevice(configurations)
c.JSON(200, models.APIResponse{
Data: device,
PTZFunctions: ptzFunctions,
CanZoom: canZoom,
CanPanTilt: canPanTilt,
})
} else {
c.JSON(400, models.APIResponse{
Message: "Something went wrong while getting the configurations " + err.Error(),
})
}
} else {
c.JSON(400, models.APIResponse{
Message: "Something went wrong while verifying the ONVIF connection " + err.Error(),
})
}
} else {
c.JSON(400, models.APIResponse{
Message: "Something went wrong while receiving the config " + err.Error(),
})
}
}

View File

@@ -42,7 +42,8 @@ func LoginToOnvif(c *gin.Context) {
},
}
device, err := onvif.ConnectToOnvifDevice(configuration)
cameraConfiguration := configuration.Config.Capture.IPCamera
device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration)
if err == nil {
c.JSON(200, gin.H{
"device": device,
@@ -85,7 +86,8 @@ func GetOnvifCapabilities(c *gin.Context) {
},
}
device, err := onvif.ConnectToOnvifDevice(configuration)
cameraConfiguration := configuration.Config.Capture.IPCamera
device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration)
if err == nil {
c.JSON(200, gin.H{
"capabilities": onvif.GetCapabilitiesFromDevice(device),
@@ -128,7 +130,8 @@ func DoOnvifPanTilt(c *gin.Context) {
},
}
device, err := onvif.ConnectToOnvifDevice(configuration)
cameraConfiguration := configuration.Config.Capture.IPCamera
device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration)
if err == nil {
// Get token from the first profile
@@ -137,13 +140,13 @@ func DoOnvifPanTilt(c *gin.Context) {
if err == nil {
// Get the configurations from the device
configurations, err := onvif.GetConfigurationsFromDevice(device)
ptzConfigurations, err := onvif.GetPTZConfigurationsFromDevice(device)
if err == nil {
pan := onvifPanTilt.Pan
tilt := onvifPanTilt.Tilt
err := onvif.ContinuousPanTilt(device, configurations, token, pan, tilt)
err := onvif.ContinuousPanTilt(device, ptzConfigurations, token, pan, tilt)
if err == nil {
c.JSON(200, models.APIResponse{
Message: "Successfully pan/tilted the camera",
@@ -201,7 +204,8 @@ func DoOnvifZoom(c *gin.Context) {
},
}
device, err := onvif.ConnectToOnvifDevice(configuration)
cameraConfiguration := configuration.Config.Capture.IPCamera
device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration)
if err == nil {
// Get token from the first profile
@@ -209,13 +213,13 @@ func DoOnvifZoom(c *gin.Context) {
if err == nil {
// Get the configurations from the device
configurations, err := onvif.GetConfigurationsFromDevice(device)
// Get the PTZ configurations from the device
ptzConfigurations, err := onvif.GetPTZConfigurationsFromDevice(device)
if err == nil {
zoom := onvifZoom.Zoom
err := onvif.ContinuousZoom(device, configurations, token, zoom)
err := onvif.ContinuousZoom(device, ptzConfigurations, token, zoom)
if err == nil {
c.JSON(200, models.APIResponse{
Message: "Successfully zoomed the camera",

View File

@@ -7,6 +7,7 @@ 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/onvif"
"github.com/kerberos-io/agent/machinery/src/routers/websocket"
"github.com/kerberos-io/agent/machinery/src/cloud"
@@ -62,19 +63,18 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configuratio
api.GET("/dashboard", func(c *gin.Context) {
// This will return the timestamp when the last packet was correctyl received
// this is to calculate if the camera connection is still working.
lastPacketReceived := int64(0)
if communication.LastPacketTimer != nil && communication.LastPacketTimer.Load() != nil {
lastPacketReceived = communication.LastPacketTimer.Load().(int64)
}
// Check if camera is online.
cameraIsOnline := communication.CameraConnected
// If an agent is properly setup with Kerberos Hub, we will send
// a ping to Kerberos Hub every 15seconds. On receiving a positive response
// it will update the CloudTimestamp value.
cloudTimestamp := int64(0)
cloudIsOnline := false
if communication.CloudTimestamp != nil && communication.CloudTimestamp.Load() != nil {
cloudTimestamp = communication.CloudTimestamp.Load().(int64)
timestamp := communication.CloudTimestamp.Load().(int64)
if timestamp > 0 {
cloudIsOnline = true
}
}
// The total number of recordings stored in the directory.
@@ -99,8 +99,8 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configuratio
c.JSON(200, gin.H{
"offlineMode": configuration.Config.Offline,
"cameraOnline": lastPacketReceived,
"cloudOnline": cloudTimestamp,
"cameraOnline": cameraIsOnline,
"cloudOnline": cloudIsOnline,
"numberOfRecordings": numberOfRecordings,
"days": days,
"latestEvents": latestEvents,
@@ -196,6 +196,10 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configuratio
})
})
api.POST("/onvif/verify", func(c *gin.Context) {
onvif.VerifyOnvifConnection(c)
})
api.POST("/hub/verify", func(c *gin.Context) {
cloud.VerifyHub(c)
})

View File

@@ -1,6 +1,8 @@
package http
import (
"os"
jwt "github.com/appleboy/gin-jwt/v2"
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/contrib/static"
@@ -57,6 +59,17 @@ func StartServer(configuration *models.Configuration, communication *models.Comm
// Add all routes
AddRoutes(r, authMiddleware, configuration, communication)
// Update environment variables
environmentVariables := "./www/env.js"
if os.Getenv("AGENT_MODE") == "demo" {
demoEnvironmentVariables := "./www/env.demo.js"
// Move demo environment variables to environment variables
err := os.Rename(demoEnvironmentVariables, environmentVariables)
if err != nil {
log.Fatal(err)
}
}
// Add static routes to UI
r.Use(static.Serve("/", static.LocalFile("./www", true)))
r.Use(static.Serve("/dashboard", static.LocalFile("./www", true)))

View File

@@ -13,10 +13,38 @@ import (
"github.com/kerberos-io/agent/machinery/src/webrtc"
)
// We'll cache the MQTT settings to know if we need to reinitialize the MQTT client connection.
// If we update the configuration but no new MQTT settings are provided, we don't need to restart it.
var PREV_MQTTURI string
var PREV_MQTTUsername string
var PREV_MQTTPassword string
var PREV_HubKey string
var PREV_AgentKey string
func HasMQTTClientModified(configuration *models.Configuration) bool {
MTTURI := configuration.Config.MQTTURI
MTTUsername := configuration.Config.MQTTUsername
MQTTPassword := configuration.Config.MQTTPassword
HubKey := configuration.Config.HubKey
AgentKey := configuration.Config.Key
if PREV_MQTTURI != MTTURI || PREV_MQTTUsername != MTTUsername || PREV_MQTTPassword != MQTTPassword || PREV_HubKey != HubKey || PREV_AgentKey != AgentKey {
log.Log.Info("HasMQTTClientModified: MQTT settings have been modified, restarting MQTT client.")
return true
}
return false
}
func ConfigureMQTT(configuration *models.Configuration, communication *models.Communication) mqtt.Client {
config := configuration.Config
// Set the MQTT settings.
PREV_MQTTURI = configuration.Config.MQTTURI
PREV_MQTTUsername = configuration.Config.MQTTUsername
PREV_MQTTPassword = configuration.Config.MQTTPassword
PREV_HubKey = configuration.Config.HubKey
PREV_AgentKey = configuration.Config.Key
if config.Offline == "true" {
log.Log.Info("ConfigureMQTT: not starting as running in Offline mode.")
} else {
@@ -78,7 +106,6 @@ func ConfigureMQTT(configuration *models.Configuration, communication *models.Co
webrtc.CandidateArrays = make(map[string](chan string))
opts.OnConnect = func(c mqtt.Client) {
// We managed to connect to the MQTT broker, hurray!
log.Log.Info("ConfigureMQTT: " + mqttClientID + " connected to " + mqttURL)
@@ -117,11 +144,15 @@ func MQTTListenerHandleLiveSD(mqttClient mqtt.Client, hubKey string, configurati
config := configuration.Config
topicRequest := "kerberos/" + hubKey + "/device/" + config.Key + "/request-live"
mqttClient.Subscribe(topicRequest, 0, func(c mqtt.Client, msg mqtt.Message) {
select {
case communication.HandleLiveSD <- time.Now().Unix():
default:
if communication.CameraConnected {
select {
case communication.HandleLiveSD <- time.Now().Unix():
default:
}
log.Log.Info("MQTTListenerHandleLiveSD: received request to livestream.")
} else {
log.Log.Info("MQTTListenerHandleLiveSD: received request to livestream, but camera is not connected.")
}
log.Log.Info("MQTTListenerHandleLiveSD: received request to livestream.")
msg.Ack()
})
}
@@ -130,12 +161,16 @@ func MQTTListenerHandleLiveHDHandshake(mqttClient mqtt.Client, hubKey string, co
config := configuration.Config
topicRequestWebRtc := config.Key + "/register"
mqttClient.Subscribe(topicRequestWebRtc, 0, func(c mqtt.Client, msg mqtt.Message) {
log.Log.Info("MQTTListenerHandleLiveHDHandshake: received request to setup webrtc.")
var sdp models.SDPPayload
json.Unmarshal(msg.Payload(), &sdp)
select {
case communication.HandleLiveHDHandshake <- sdp:
default:
if communication.CameraConnected {
var sdp models.SDPPayload
json.Unmarshal(msg.Payload(), &sdp)
select {
case communication.HandleLiveHDHandshake <- sdp:
default:
}
log.Log.Info("MQTTListenerHandleLiveHDHandshake: received request to setup webrtc.")
} else {
log.Log.Info("MQTTListenerHandleLiveHDHandshake: received request to setup webrtc, but camera is not connected.")
}
msg.Ack()
})
@@ -145,9 +180,13 @@ func MQTTListenerHandleLiveHDKeepalive(mqttClient mqtt.Client, hubKey string, co
config := configuration.Config
topicKeepAlive := fmt.Sprintf("kerberos/webrtc/keepalivehub/%s", config.Key)
mqttClient.Subscribe(topicKeepAlive, 0, func(c mqtt.Client, msg mqtt.Message) {
alive := string(msg.Payload())
communication.HandleLiveHDKeepalive <- alive
log.Log.Info("MQTTListenerHandleLiveHDKeepalive: Received keepalive: " + alive)
if communication.CameraConnected {
alive := string(msg.Payload())
communication.HandleLiveHDKeepalive <- alive
log.Log.Info("MQTTListenerHandleLiveHDKeepalive: Received keepalive: " + alive)
} else {
log.Log.Info("MQTTListenerHandleLiveHDKeepalive: received keepalive, but camera is not connected.")
}
})
}
@@ -155,9 +194,13 @@ func MQTTListenerHandleLiveHDPeers(mqttClient mqtt.Client, hubKey string, config
config := configuration.Config
topicPeers := fmt.Sprintf("kerberos/webrtc/peers/%s", config.Key)
mqttClient.Subscribe(topicPeers, 0, func(c mqtt.Client, msg mqtt.Message) {
peerCount := string(msg.Payload())
communication.HandleLiveHDPeers <- peerCount
log.Log.Info("MQTTListenerHandleLiveHDPeers: Number of peers listening: " + peerCount)
if communication.CameraConnected {
peerCount := string(msg.Payload())
communication.HandleLiveHDPeers <- peerCount
log.Log.Info("MQTTListenerHandleLiveHDPeers: Number of peers listening: " + peerCount)
} else {
log.Log.Info("MQTTListenerHandleLiveHDPeers: received peer count, but camera is not connected.")
}
})
}
@@ -165,19 +208,23 @@ func MQTTListenerHandleLiveHDCandidates(mqttClient mqtt.Client, hubKey string, c
config := configuration.Config
topicCandidates := "candidate/cloud"
mqttClient.Subscribe(topicCandidates, 0, func(c mqtt.Client, msg mqtt.Message) {
var candidate models.Candidate
json.Unmarshal(msg.Payload(), &candidate)
if candidate.CloudKey == config.Key {
key := candidate.CloudKey + "/" + candidate.Cuuid
candidatesExists := false
var channel chan string
for !candidatesExists {
webrtc.CandidatesMutex.Lock()
channel, candidatesExists = webrtc.CandidateArrays[key]
webrtc.CandidatesMutex.Unlock()
if communication.CameraConnected {
var candidate models.Candidate
json.Unmarshal(msg.Payload(), &candidate)
if candidate.CloudKey == config.Key {
key := candidate.CloudKey + "/" + candidate.Cuuid
candidatesExists := false
var channel chan string
for !candidatesExists {
webrtc.CandidatesMutex.Lock()
channel, candidatesExists = webrtc.CandidateArrays[key]
webrtc.CandidatesMutex.Unlock()
}
log.Log.Info("MQTTListenerHandleLiveHDCandidates: " + string(msg.Payload()))
channel <- string(msg.Payload())
}
log.Log.Info("MQTTListenerHandleLiveHDCandidates: " + string(msg.Payload()))
channel <- string(msg.Payload())
} else {
log.Log.Info("MQTTListenerHandleLiveHDCandidates: received candidate, but camera is not connected.")
}
})
}
@@ -186,23 +233,28 @@ func MQTTListenerHandleONVIF(mqttClient mqtt.Client, hubKey string, configuratio
config := configuration.Config
topicOnvif := fmt.Sprintf("kerberos/onvif/%s", config.Key)
mqttClient.Subscribe(topicOnvif, 0, func(c mqtt.Client, msg mqtt.Message) {
var onvifAction models.OnvifAction
json.Unmarshal(msg.Payload(), &onvifAction)
communication.HandleONVIF <- onvifAction
log.Log.Info("MQTTListenerHandleONVIF: Received an action - " + onvifAction.Action)
if communication.CameraConnected {
var onvifAction models.OnvifAction
json.Unmarshal(msg.Payload(), &onvifAction)
communication.HandleONVIF <- onvifAction
log.Log.Info("MQTTListenerHandleONVIF: Received an action - " + onvifAction.Action)
} else {
log.Log.Info("MQTTListenerHandleONVIF: received action, but camera is not connected.")
}
})
}
func DisconnectMQTT(mqttClient mqtt.Client, config *models.Config) {
if mqttClient != nil {
// Cleanup all subscriptions.
mqttClient.Unsubscribe("kerberos/" + config.HubKey + "/device/" + config.Key + "/request-live")
mqttClient.Unsubscribe(config.Key + "/register")
mqttClient.Unsubscribe("kerberos/webrtc/keepalivehub/" + config.Key)
mqttClient.Unsubscribe("kerberos/webrtc/peers/" + config.Key)
// Cleanup all subscriptions
mqttClient.Unsubscribe("kerberos/" + PREV_HubKey + "/device/" + PREV_AgentKey + "/request-live")
mqttClient.Unsubscribe(PREV_AgentKey + "/register")
mqttClient.Unsubscribe("kerberos/webrtc/keepalivehub/" + PREV_AgentKey)
mqttClient.Unsubscribe("kerberos/webrtc/peers/" + PREV_AgentKey)
mqttClient.Unsubscribe("candidate/cloud")
mqttClient.Unsubscribe("kerberos/onvif/" + config.Key)
mqttClient.Unsubscribe("kerberos/onvif/" + PREV_AgentKey)
mqttClient.Disconnect(1000)
mqttClient = nil
log.Log.Info("DisconnectMQTT: MQTT client disconnected.")
}
}

View File

@@ -51,6 +51,7 @@ 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()
@@ -83,28 +84,29 @@ func WebsocketHandler(c *gin.Context, communication *models.Communication) {
_, exists := sockets[clientID].Cancels["stream-sd"]
if exists {
sockets[clientID].Cancels["stream-sd"]()
delete(sockets[clientID].Cancels, "stream-sd")
} else {
log.Log.Error("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)
if communication.CameraConnected {
_, exists := sockets[clientID].Cancels["stream-sd"]
if exists {
log.Log.Info("Already streaming sd for " + clientID)
} else {
startStream := Message{
ClientID: clientID,
MessageType: "stream-sd",
Message: map[string]string{
"message": "Start streaming low resolution",
},
}
sockets[clientID].WriteJson(startStream)
_, exists := sockets[clientID].Cancels["stream-sd"]
if exists {
log.Log.Info("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)
ctx, cancel := context.WithCancel(context.Background())
sockets[clientID].Cancels["stream-sd"] = cancel
go ForwardSDStream(ctx, clientID, sockets[clientID], communication)
}
}
}
@@ -173,5 +175,14 @@ logreader:
frame.Free()
// Close socket for streaming
_, exists := connection.Cancels["stream-sd"]
if exists {
delete(connection.Cancels, "stream-sd")
} else {
log.Log.Error("Streaming sd does not exists for " + clientID)
}
// Send stop streaming message
log.Log.Info("ForwardSDStream: stop sending streaming over websocket")
}

View File

@@ -21,6 +21,7 @@
"react/forbid-prop-types": "off",
"no-case-declarations": "off",
"camelcase": "off",
"dot-notation": "off",
"jsx-a11y/media-has-caption": "off",
"jsx-a11y/anchor-is-valid": "off",
"jsx-a11y/click-events-have-key-events": "off",

View File

@@ -4,7 +4,7 @@
"private": false,
"dependencies": {
"@giantmachines/redux-websocket": "^1.5.1",
"@kerberos-io/ui": "^1.72.0",
"@kerberos-io/ui": "^1.76.0",
"@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.3",
"@testing-library/jest-dom": "^5.16.5",

5
ui/public/env.demo.js Normal file
View File

@@ -0,0 +1,5 @@
(function (window) {
window['env'] = window['env'] || {};
// Environment variables
window['env']['mode'] = 'demo';
})(this);

5
ui/public/env.js Normal file
View File

@@ -0,0 +1,5 @@
(function (window) {
window['env'] = window['env'] || {};
// Environment variables
window['env']['mode'] = 'release';
})(this);

View File

@@ -19,7 +19,7 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<script src="assets/env.js"></script>
<script src="%PUBLIC_URL%/env.js"></script>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.

View File

@@ -4,7 +4,8 @@
"configure": "Konfigurieren"
},
"buttons": {
"save": "Speichern"
"save": "Speichern",
"verify_connection": "Verify Connection"
},
"navigation": {
"profile": "Profil",
@@ -69,7 +70,10 @@
"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"
"verify_camera_error": "Beim überprüfen der Kameraeinstellungen ist ein Fehler aufgetreten",
"verify_onvif": "Verifying your ONVIF settings.",
"verify_onvif_success": "ONVIF settings are successfully verified.",
"verify_onvif_error": "Something went wrong while verifying the ONVIF settings"
},
"overview": {
"general": "Allgemein",
@@ -186,13 +190,13 @@
"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_username": "Benutzername/Verzeichnis (should match Kerberos Hub username)",
"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_directory": "Verzeichnis (should match Kerberos Hub username)",
"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..",

View File

@@ -4,7 +4,8 @@
"configure": "Configure"
},
"buttons": {
"save": "Save"
"save": "Save",
"verify_connection": "Verify Connection"
},
"navigation": {
"profile": "Profile",
@@ -69,7 +70,10 @@
"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"
"verify_camera_error": "Something went wrong while verifying the camera settings",
"verify_onvif": "Verifying your ONVIF settings.",
"verify_onvif_success": "ONVIF settings are successfully verified.",
"verify_onvif_error": "Something went wrong while verifying the ONVIF settings"
},
"overview": {
"general": "General",
@@ -186,13 +190,13 @@
"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_username": "Username/Directory (should match Kerberos Hub username)",
"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_directory": "Directory (should match Kerberos Hub username)",
"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.",

View File

@@ -4,7 +4,8 @@
"configure": "Configure"
},
"buttons": {
"save": "Save"
"save": "Save",
"verify_connection": "Verify Connection"
},
"navigation": {
"profile": "Perfil",
@@ -69,7 +70,10 @@
"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"
"verify_camera_error": "Something went wrong while verifying the camera settings",
"verify_onvif": "Verifying your ONVIF settings.",
"verify_onvif_success": "ONVIF settings are successfully verified.",
"verify_onvif_error": "Something went wrong while verifying the ONVIF settings"
},
"overview": {
"general": "General",
@@ -186,13 +190,13 @@
"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_username": "Username/Directory (should match Kerberos Hub username)",
"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_directory": "Directory (should match Kerberos Hub username)",
"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.",

View File

@@ -69,7 +69,10 @@
"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"
"verify_camera_error": "Quelque chose s'est mal déroulé lors de la vérification des paramètres de la caméra",
"verify_onvif": "Verifying your ONVIF settings.",
"verify_onvif_success": "ONVIF settings are successfully verified.",
"verify_onvif_error": "Something went wrong while verifying the ONVIF settings"
},
"overview": {
"general": "Général",
@@ -186,13 +189,13 @@
"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_username": "Utilisateur/Répertoire (should match Kerberos Hub username)",
"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_directory": "Répertoire (should match Kerberos Hub username)",
"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.",

View File

@@ -4,7 +4,8 @@
"configure": "設定"
},
"buttons": {
"save": "保存"
"save": "保存",
"verify_connection": "Verify Connection"
},
"navigation": {
"profile": "プロフィール",
@@ -69,7 +70,10 @@
"verify_persistence_error": "持続性の検証中に問題が発生しました",
"verify_camera": "カメラの設定を確認しています。",
"verify_camera_success": "カメラの設定が正常に検証されました。",
"verify_camera_error": "カメラ設定の確認中に問題が発生しました"
"verify_camera_error": "カメラ設定の確認中に問題が発生しました",
"verify_onvif": "Verifying your ONVIF settings.",
"verify_onvif_success": "ONVIF settings are successfully verified.",
"verify_onvif_error": "Something went wrong while verifying the ONVIF settings"
},
"overview": {
"general": "全般的",
@@ -186,13 +190,13 @@
"kerberoshub_description_region": "録音を保存しているリージョン。",
"kerberoshub_bucket": "bucket",
"kerberoshub_description_bucket": "録音を保存しているbucket",
"kerberoshub_username": "ユーザー名/ディレクトリ",
"kerberoshub_username": "ユーザー名/ディレクトリ (should match Kerberos Hub username)",
"kerberoshub_description_username": "Kerberos Hub アカウントのユーザー名。",
"kerberosvault_apiurl": "Kerberos ボールト API URL",
"kerberosvault_description_apiurl": "Kerberos ボールト API",
"kerberosvault_provider": "プロバイダ",
"kerberosvault_description_provider": "録音の送信先のプロバイダー。",
"kerberosvault_directory": "ディレクトリ",
"kerberosvault_directory": "ディレクトリ (should match Kerberos Hub username)",
"kerberosvault_description_directory": "録音がプロバイダーに保存されるサブディレクトリ。",
"kerberosvault_accesskey": "アクセスキー",
"kerberosvault_description_accesskey": "Kerberos Vault アカウントのアクセス キー。",

View File

@@ -4,7 +4,8 @@
"configure": "Instellingen"
},
"buttons": {
"save": "Opslaan"
"save": "Opslaan",
"verify_connection": "Verifieer Connectie"
},
"navigation": {
"profile": "Profiel",
@@ -69,7 +70,10 @@
"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."
"verify_camera_error": "Er ging iets mis met de camera instellingen.",
"verify_onvif": "Verifying your ONVIF settings.",
"verify_onvif_success": "ONVIF settings are successfully verified.",
"verify_onvif_error": "Something went wrong while verifying the ONVIF settings"
},
"overview": {
"general": "Algemeen",
@@ -187,13 +191,13 @@
"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_username": "Gebruikersnaam/Map (moet overeenkomen met de Kerberos Hub username)",
"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_directory": "Map (moet overeenkomen met de Kerberos Hub username)",
"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.",

View File

@@ -4,7 +4,8 @@
"configure": "Configure"
},
"buttons": {
"save": "Save"
"save": "Save",
"verify_connection": "Verify Connection"
},
"navigation": {
"profile": "Profil",
@@ -69,7 +70,10 @@
"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"
"verify_camera_error": "Something went wrong while verifying the camera settings",
"verify_onvif": "Verifying your ONVIF settings.",
"verify_onvif_success": "ONVIF settings are successfully verified.",
"verify_onvif_error": "Something went wrong while verifying the ONVIF settings"
},
"overview": {
"general": "General",
@@ -186,13 +190,13 @@
"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_username": "Username/Directory (should match Kerberos Hub username)",
"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_directory": "Directory (should match Kerberos Hub username)",
"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.",

View File

@@ -4,7 +4,8 @@
"configure": "Configurar"
},
"buttons": {
"save": "Salvar"
"save": "Salvar",
"verify_connection": "Verify Connection"
},
"navigation": {
"profile": "Perfil",
@@ -69,7 +70,10 @@
"verify_persistence_error": "Algo deu errado ao verificar o armazenamento",
"verify_camera": "Verificando as configurações de sua câmera.",
"verify_camera_success": "As configurações da câmera foram verificadas com sucesso.",
"verify_camera_error": "Algo deu errado ao verificar as configurações da câmera."
"verify_camera_error": "Algo deu errado ao verificar as configurações da câmera.",
"verify_onvif": "Verifying your ONVIF settings.",
"verify_onvif_success": "ONVIF settings are successfully verified.",
"verify_onvif_error": "Something went wrong while verifying the ONVIF settings"
},
"overview": {
"general": "Geral",
@@ -186,13 +190,13 @@
"kerberoshub_description_region": "A região em que estamos armazenando nossas gravações.",
"kerberoshub_bucket": "Bucket",
"kerberoshub_description_bucket": "O bucket no qual estamos armazenando nossas gravações.",
"kerberoshub_username": "Nome de usuário/diretório (Username/Directory)",
"kerberoshub_username": "Nome de usuário/diretório (should match Kerberos Hub username)",
"kerberoshub_description_username": "O nome de usuário da sua conta do Kerberos Hub.",
"kerberosvault_apiurl": "Url da API do Kerberos Vault",
"kerberosvault_description_apiurl": "a API Kerberos Vault",
"kerberosvault_provider": "Provedor",
"kerberosvault_description_provider": "O provedor para o qual suas gravações serão enviadas.",
"kerberosvault_directory": "Diretório",
"kerberosvault_directory": "Diretório (should match Kerberos Hub username)",
"kerberosvault_description_directory": "Subdiretório as gravações serão armazenadas em seu provedor.",
"kerberosvault_accesskey": "Chave de acesso(Access key)",
"kerberosvault_description_accesskey": "A chave de acesso da sua conta do Kerberos Vault.",

View File

@@ -4,7 +4,8 @@
"configure": "配置"
},
"buttons": {
"save": "保存"
"save": "保存",
"verify_connection": "Verify Connection"
},
"navigation": {
"profile": "配置文件",
@@ -69,7 +70,10 @@
"verify_persistence_error": "验证持久化存储时出错",
"verify_camera": "验证您的相机设置",
"verify_camera_success": "相机设置验证成功",
"verify_camera_error": "验证相机设置时出错"
"verify_camera_error": "验证相机设置时出错",
"verify_onvif": "Verifying your ONVIF settings.",
"verify_onvif_success": "ONVIF settings are successfully verified.",
"verify_onvif_error": "Something went wrong while verifying the ONVIF settings"
},
"overview": {
"general": "常规",
@@ -186,13 +190,13 @@
"kerberoshub_description_region": "存储录像的区域",
"kerberoshub_bucket": "Bucket",
"kerberoshub_description_bucket": "存储录像的桶",
"kerberoshub_username": "账户/目录",
"kerberoshub_username": "账户/目录 (should match Kerberos Hub username)",
"kerberoshub_description_username": "您的 Kerberos Hub 帐户的用户名",
"kerberosvault_apiurl": "Kerberos Vault API URL",
"kerberosvault_description_apiurl": "Kerberos Vault API",
"kerberosvault_provider": "供应商",
"kerberosvault_description_provider": "您的录像将会被发送到的提供商",
"kerberosvault_directory": "目录",
"kerberosvault_directory": "目录 (should match Kerberos Hub username)",
"kerberosvault_description_directory": "录像将存储在提供商中的子目录",
"kerberosvault_accesskey": "访问密钥",
"kerberosvault_description_accesskey": "Kerberos Vault 帐户的访问密钥",

View File

@@ -38,6 +38,16 @@ class App extends React.Component {
dispatchGetDashboardInformation();
dispatchConnect();
const connectInterval = interval(1000);
this.connectionSubscription = connectInterval.subscribe(() => {
const { connected } = this.props;
if (connected) {
// Already connected
} else {
dispatchConnect();
}
});
const interval$ = interval(5000);
this.subscription = interval$.subscribe(() => {
dispatchGetDashboardInformation();
@@ -64,6 +74,7 @@ class App extends React.Component {
componentWillUnmount() {
this.subscription.unsubscribe();
this.connectionSubscription.unsubscribe();
const message = {
client_id: uuid(),
message_type: 'goodbye',
@@ -82,111 +93,122 @@ class App extends React.Component {
const { children, username, dashboard, dispatchLogout } = this.props;
const cloudOnline = this.getCurrentTimestamp() - dashboard.cloudOnline < 30;
return (
<div id="page-root">
<Sidebar logo={logo} title="Kerberos Agent" version="v1-beta" mobile>
<Profilebar
username={username}
email="support@kerberos.io"
userrole={t('navigation.admin')}
logout={dispatchLogout}
/>
<Navigation>
<NavigationSection title={t('navigation.management')} />
<NavigationGroup>
<NavigationItem
title={t('navigation.dashboard')}
icon="dashboard"
link="dashboard"
/>
<NavigationItem
title={t('navigation.recordings')}
icon="media"
link="media"
/>
<NavigationItem
title={t('navigation.settings')}
icon="preferences"
link="settings"
/>
</NavigationGroup>
<NavigationSection title={t('navigation.help_support')} />
<NavigationGroup>
<NavigationItem
title={t('navigation.swagger')}
icon="api"
external
link={`${config.URL}/swagger/index.html`}
/>
<NavigationItem
title={t('navigation.documentation')}
icon="book"
external
link="https://doc.kerberos.io/agent/announcement"
/>
<NavigationItem
title="Kerberos Hub"
icon="cloud"
external
link="https://app.kerberos.io"
/>
<NavigationItem
title={t('navigation.ui_library')}
icon="paint"
external
link="https://ui.kerberos.io/"
/>
<NavigationItem
title="Github"
icon="github-nav"
external
link="https://github.com/kerberos-io/agent"
/>
</NavigationGroup>
<NavigationSection title={t('navigation.layout')} />
<NavigationGroup>
<LanguageSelect />
</NavigationGroup>
<NavigationSection title="Websocket" />
<NavigationGroup>
<div className="websocket-badge">
<Badge
title={connected ? 'connected' : 'disconnected'}
status={connected ? 'success' : 'warning'}
<>
{config.MODE !== 'release' && (
<div className={`environment ${config.MODE}`}>
Environment: {config.MODE}
</div>
)}
<div id="page-root">
<Sidebar logo={logo} title="Kerberos Agent" version="v1-beta" mobile>
<Profilebar
username={username}
email="support@kerberos.io"
userrole={t('navigation.admin')}
logout={dispatchLogout}
/>
<Navigation>
<NavigationSection title={t('navigation.management')} />
<NavigationGroup>
<NavigationItem
title={t('navigation.dashboard')}
icon="dashboard"
link="dashboard"
/>
</div>
</NavigationGroup>
</Navigation>
</Sidebar>
<Main>
<Gradient />
<NavigationItem
title={t('navigation.recordings')}
icon="media"
link="media"
/>
<NavigationItem
title={t('navigation.settings')}
icon="preferences"
link="settings"
/>
</NavigationGroup>
<NavigationSection title={t('navigation.help_support')} />
<NavigationGroup>
<NavigationItem
title={t('navigation.swagger')}
icon="api"
external
link={`${config.URL}/swagger/index.html`}
/>
<NavigationItem
title={t('navigation.documentation')}
icon="book"
external
link="https://doc.kerberos.io/agent/announcement"
/>
<NavigationItem
title="Kerberos Hub"
icon="cloud"
external
link="https://app.kerberos.io"
/>
<NavigationItem
title={t('navigation.ui_library')}
icon="paint"
external
link="https://ui.kerberos.io/"
/>
<NavigationItem
title="Github"
icon="github-nav"
external
link="https://github.com/kerberos-io/agent"
/>
</NavigationGroup>
<NavigationSection title={t('navigation.layout')} />
<NavigationGroup>
<LanguageSelect />
</NavigationGroup>
{!cloudOnline && (
<a href="https://app.kerberos.io" target="_blank" rel="noreferrer">
<div className="cloud-not-installed">
<div>
<Icon label="cloud" />
Activate Kerberos Hub, and make your cameras and recordings
available through a secured cloud!
<NavigationSection title="Websocket" />
<NavigationGroup>
<div className="websocket-badge">
<Badge
title={connected ? 'connected' : 'disconnected'}
status={connected ? 'success' : 'warning'}
/>
</div>
</div>
</a>
)}
</NavigationGroup>
</Navigation>
</Sidebar>
<Main>
<Gradient />
{dashboard.offlineMode === 'true' && (
<Link to="/settings">
<div className="offline-mode">
<div>
<Icon label="info" />
Attention! Kerberos is currently running in Offline mode.
{!cloudOnline && (
<a
href="https://app.kerberos.io"
target="_blank"
rel="noreferrer"
>
<div className="cloud-not-installed">
<div>
<Icon label="cloud" />
Activate Kerberos Hub, and make your cameras and recordings
available through a secured cloud!
</div>
</div>
</div>
</Link>
)}
</a>
)}
<MainBody>{children}</MainBody>
</Main>
</div>
{dashboard.offlineMode === 'true' && (
<Link to="/settings">
<div className="offline-mode">
<div>
<Icon label="info" />
Attention! Kerberos is currently running in Offline mode.
</div>
</div>
</Link>
)}
<MainBody>{children}</MainBody>
</Main>
</div>
</>
);
}
}

View File

@@ -1,6 +1,7 @@
import {
doGetConfig,
doSaveConfig,
doVerifyOnvif,
doVerifyHub,
doVerifyPersistence,
doGetKerberosAgentTags,
@@ -39,6 +40,28 @@ export const updateRegion = (id, polygon) => {
};
};
export const verifyOnvif = (config, onSuccess, onError) => {
return (dispatch) => {
doVerifyOnvif(
config,
(data) => {
dispatch({
type: 'VERIFY_ONVIF',
});
if (onSuccess) {
onSuccess(data);
}
},
(error) => {
const { message } = error;
if (onError) {
onError(message);
}
}
);
};
};
export const verifyCamera = (streamType, config, onSuccess, onError) => {
return (dispatch) => {
doVerifyCamera(

View File

@@ -91,6 +91,26 @@ export function doVerifyHub(config, onSuccess, onError) {
});
}
export function doVerifyOnvif(config, onSuccess, onError) {
const endpoint = API.post(`onvif/verify`, {
...config,
});
endpoint
.then((res) => {
if (res.status !== 200) {
throw new Error(res.data);
}
return res.data;
})
.then((data) => {
onSuccess(data);
})
.catch((e) => {
const { data } = e.response;
onError(data);
});
}
export function doVerifyCamera(streamType, config, onSuccess, onError) {
const cameraStreams = {
rtsp: '',

View File

@@ -12,6 +12,7 @@ const dev = {
API_URL: `${protocol}//${hostname}:8080/api`,
URL: `${protocol}//${hostname}:8080`,
WS_URL: `${websocketprotocol}//${hostname}:8080/ws`,
MODE: window['env']['mode'],
// Uncomment, and comment the above lines, when using codespaces or other special DNS names (which you can't control)
// HOSTNAME: externalHost,
// API_URL: `${protocol}//${externalHost}/api`,
@@ -25,6 +26,7 @@ const prod = {
API_URL: `${protocol}//${host}/api`,
URL: `${protocol}//${host}`,
WS_URL: `${websocketprotocol}//${host}/ws`,
MODE: window['env']['mode'],
};
const config = process.env.REACT_APP_ENVIRONMENT === 'production' ? prod : dev;

View File

@@ -50,7 +50,9 @@ function getAuthState() {
}
}
const reduxWebsocketMiddleware = reduxWebsocket();
const reduxWebsocketMiddleware = reduxWebsocket({
reconnectOnClose: true,
});
const store = createStore(
rootReducer(history),

View File

@@ -144,3 +144,55 @@ body {
padding: 0;
font-family: 'Inter', sans-serif; // use regular Inter font by default..
}
.environment.develop {
background: repeating-linear-gradient(
-55deg,
#222,
#222 10px,
#035e1f 10px,
#035e1f 20px
);
text-align: center;
color: rgba(255, 255, 255, 0.75);
padding: size(1) 0;
}
.environment.demo {
background: repeating-linear-gradient(
-55deg,
#222,
#222 10px,
#035e1f 10px,
#035e1f 20px
);
text-align: center;
color: rgba(255, 255, 255, 0.75);
padding: 12px 0;
}
.environment.staging {
background: repeating-linear-gradient(
-55deg,
#222,
#222 10px,
#6d0e0e 10px,
#6d0e0e 20px
);
text-align: center;
color: white;
padding: 12px 0;
}
.environment.acceptance {
background: repeating-linear-gradient(
-55deg,
#222,
#222 10px,
#8b8203 10px,
#8b8203 20px
);
text-align: center;
color: white;
padding: 12px 0;
}

View File

@@ -4,6 +4,7 @@ import { Link, withRouter } from 'react-router-dom';
import { withTranslation } from 'react-i18next';
import { send } from '@giantmachines/redux-websocket';
import { connect } from 'react-redux';
import { interval } from 'rxjs';
import {
Breadcrumb,
KPI,
@@ -34,7 +35,9 @@ class Dashboard extends React.Component {
liveviewLoaded: false,
open: false,
currentRecording: '',
initialised: false,
};
this.initialiseLiveview = this.initialiseLiveview.bind(this);
}
componentDidMount() {
@@ -46,27 +49,11 @@ class Dashboard extends React.Component {
});
});
}
const { connected } = this.props;
if (connected === true) {
const { dispatchSend } = this.props;
const message = {
message_type: 'stream-sd',
};
dispatchSend(message);
}
this.initialiseLiveview();
}
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);
}
componentDidUpdate() {
this.initialiseLiveview();
}
componentWillUnmount() {
@@ -75,6 +62,9 @@ class Dashboard extends React.Component {
liveview[0].remove();
}
if (this.requestStreamSubscription) {
this.requestStreamSubscription.unsubscribe();
}
const { dispatchSend } = this.props;
const message = {
message_type: 'stop-sd',
@@ -93,6 +83,30 @@ class Dashboard extends React.Component {
return Math.round(Date.now() / 1000);
}
initialiseLiveview() {
const { initialised } = this.state;
if (!initialised) {
const message = {
message_type: 'stream-sd',
};
const { connected, dispatchSend } = this.props;
if (connected) {
dispatchSend(message);
}
const requestStreamInterval = interval(2000);
this.requestStreamSubscription = requestStreamInterval.subscribe(() => {
const { connected: isConnected } = this.props;
if (isConnected) {
dispatchSend(message);
}
});
this.setState({
initialised: true,
});
}
}
openModal(file) {
this.setState({
open: true,
@@ -106,17 +120,16 @@ class Dashboard extends React.Component {
// We check if the camera was getting a valid frame
// during the last 5 seconds, otherwise we assume the camera is offline.
const isCameraOnline =
this.getCurrentTimestamp() - dashboard.cameraOnline < 15;
const isCameraOnline = dashboard.cameraOnline;
// We check if a connection is made to Kerberos Hub, or if Offline mode
// has been turned on.
const cloudOnline = this.getCurrentTimestamp() - dashboard.cloudOnline < 30;
const isCloudOnline = dashboard.cloudOnline;
let cloudConnection = t('dashboard.not_connected');
if (dashboard.offlineMode === 'true') {
cloudConnection = t('dashboard.offline_mode');
} else {
cloudConnection = cloudOnline
cloudConnection = isCloudOnline
? t('dashboard.connected')
: t('dashboard.not_connected');
}
@@ -176,7 +189,7 @@ class Dashboard extends React.Component {
subtitle={cloudConnection}
footer="Cloud"
icon={
cloudOnline && dashboard.offlineMode !== 'true'
isCloudOnline && dashboard.offlineMode !== 'true'
? 'circle-check-big'
: 'circle-cross-big'
}
@@ -297,7 +310,7 @@ class Dashboard extends React.Component {
</div>
<div>
<h2>{t('dashboard.live_view')}</h2>
{!liveviewLoaded && (
{(!liveviewLoaded || !isCameraOnline) && (
<SetupBox
btnicon="preferences"
btnlabel={t('dashboard.configure_connection')}
@@ -307,7 +320,12 @@ class Dashboard extends React.Component {
text={t('dashboard.loading_live_view_description')}
/>
)}
<div style={{ visibility: liveviewLoaded ? 'visible' : 'hidden' }}>
<div
style={{
visibility:
liveviewLoaded && isCameraOnline ? 'visible' : 'hidden',
}}
>
<ImageCard
imageSrc={`data:image/png;base64, ${
images.length ? images[0] : ''

View File

@@ -49,60 +49,98 @@ class Login extends React.Component {
const { loginError, error } = this.props;
return (
<LandingLayout
title="Kerberos Agent"
version={config.VERSION}
description="Video surveillance for everyone"
>
<section className="login-body">
<Block>
<form onSubmit={this.handleSubmit} noValidate>
<BlockHeader>
<div>
<Icon label="login" /> <h4>Login</h4>
</div>
</BlockHeader>
{loginError && (
<AlertMessage
message={error}
onClick={() => this.hideMessage()}
/>
)}
<BlockBody>
<Input
label="username or email"
placeholder="Your username/email"
readonly={false}
disabled={false}
type="text"
name="username"
iconleft="accounts"
/>
<Input
label="password"
placeholder="Your password"
readonly={false}
disabled={false}
type="password"
name="password"
iconleft="locked"
iconright="activity"
seperate
/>
</BlockBody>
<BlockFooter>
<p />
<Button
buttonType="submit"
type="submit"
icon="logout"
label="Login"
/>
</BlockFooter>
</form>
</Block>
</section>
</LandingLayout>
<>
{config.MODE !== 'release' && (
<div className={`environment ${config.MODE}`}>
Environment: {config.MODE}
</div>
)}
<LandingLayout
title="Kerberos Agent"
version={config.VERSION}
description="Video surveillance for everyone"
>
<section className="login-body">
<Block>
<form onSubmit={this.handleSubmit} noValidate>
<BlockHeader>
<div>
<Icon label="login" /> <h4>Login</h4>
</div>
</BlockHeader>
{loginError && (
<AlertMessage
message={error}
onClick={() => this.hideMessage()}
/>
)}
<BlockBody>
{config.MODE === 'demo' && (
<>
<Input
label="username or email"
placeholder="Your username/email"
readonly
disabled={false}
value="root"
type="text"
name="username"
iconleft="accounts"
/>
<Input
label="password"
placeholder="Your password"
readonly
disabled={false}
value="root"
type="password"
name="password"
iconleft="locked"
iconright="activity"
seperate
/>
</>
)}
{config.MODE !== 'demo' && (
<>
<Input
label="username or email"
placeholder="Your username/email"
readonly={false}
disabled={false}
type="text"
name="username"
iconleft="accounts"
/>
<Input
label="password"
placeholder="Your password"
readonly={false}
disabled={false}
type="password"
name="password"
iconleft="locked"
iconright="activity"
seperate
/>
</>
)}
</BlockBody>
<BlockFooter>
<p />
<Button
buttonType="submit"
type="submit"
icon="logout"
label="Login"
/>
</BlockFooter>
</form>
</Block>
</section>
</LandingLayout>
</>
);
}
}

View File

@@ -27,6 +27,7 @@ import {
updateRegion,
removeRegion,
saveConfig,
verifyOnvif,
verifyCamera,
verifyHub,
verifyPersistence,
@@ -63,6 +64,9 @@ class Settings extends React.Component {
verifyCameraSuccess: false,
verifyCameraError: false,
verifyCameraMessage: '',
verifyOnvifSuccess: false,
verifyOnvifError: false,
verifyOnvifErrorMessage: '',
loading: false,
loadingHub: false,
loadingCamera: false,
@@ -127,6 +131,7 @@ class Settings extends React.Component {
this.onAddRegion = this.onAddRegion.bind(this);
this.onUpdateRegion = this.onUpdateRegion.bind(this);
this.onDeleteRegion = this.onDeleteRegion.bind(this);
this.verifyONVIF = this.verifyONVIF.bind(this);
}
componentDidMount() {
@@ -224,13 +229,15 @@ class Settings extends React.Component {
calculateTimetable(timetable) {
this.timetable = timetable;
for (let i = 0; i < timetable.length; i += 1) {
const time = timetable[i];
const { start1, start2, end1, end2 } = time;
this.timetable[i].start1Full = this.convertSecondsToHourMinute(start1);
this.timetable[i].start2Full = this.convertSecondsToHourMinute(start2);
this.timetable[i].end1Full = this.convertSecondsToHourMinute(end1);
this.timetable[i].end2Full = this.convertSecondsToHourMinute(end2);
if (this.timetable) {
for (let i = 0; i < timetable.length; i += 1) {
const time = timetable[i];
const { start1, start2, end1, end2 } = time;
this.timetable[i].start1Full = this.convertSecondsToHourMinute(start1);
this.timetable[i].start2Full = this.convertSecondsToHourMinute(start2);
this.timetable[i].end1Full = this.convertSecondsToHourMinute(end1);
this.timetable[i].end2Full = this.convertSecondsToHourMinute(end2);
}
}
}
@@ -272,6 +279,8 @@ class Settings extends React.Component {
verifyHubError: false,
configSuccess: false,
configError: false,
verifyOnvifSuccess: false,
verifyOnvifError: false,
});
if (config) {
@@ -293,6 +302,52 @@ class Settings extends React.Component {
}
}
verifyONVIF() {
const { config, dispatchVerifyOnvif } = this.props;
// Get camera configuration (subset of config).
const cameraConfig = {
onvif_xaddr: config.config.capture.ipcamera.onvif_xaddr,
onvif_username: config.config.capture.ipcamera.onvif_username,
onvif_password: config.config.capture.ipcamera.onvif_password,
};
this.setState({
verifyOnvifSuccess: false,
verifyOnvifError: false,
verifyOnvifErrorMessage: '',
verifyCameraSuccess: false,
verifyCameraError: false,
verifyCameraErrorMessage: '',
configSuccess: false,
configError: false,
loadingCamera: false,
loadingOnvif: true,
});
if (config) {
dispatchVerifyOnvif(
cameraConfig,
() => {
this.setState({
verifyOnvifSuccess: true,
verifyOnvifError: false,
verifyOnvifErrorMessage: '',
loadingOnvif: false,
});
},
(error) => {
this.setState({
verifyOnvifSuccess: false,
verifyOnvifError: true,
verifyOnvifErrorMessage: error,
loadingOnvif: false,
});
}
);
}
}
verifyHubSettings() {
const { config, dispatchVerifyHub } = this.props;
if (config) {
@@ -315,6 +370,8 @@ class Settings extends React.Component {
verifyCameraSuccess: false,
verifyCameraError: false,
verifyCameraErrorMessage: '',
verifyOnvifSuccess: false,
verifyOnvifError: false,
loadingHub: true,
});
@@ -360,6 +417,8 @@ class Settings extends React.Component {
persistenceError: false,
verifyCameraSuccess: false,
verifyCameraError: false,
verifyOnvifSuccess: false,
verifyOnvifError: false,
verifyCameraErrorMessage: '',
loading: true,
});
@@ -400,6 +459,7 @@ class Settings extends React.Component {
this.setState({
configSuccess: false,
configError: false,
loadingCamera: true,
verifyPersistenceSuccess: false,
verifyPersistenceError: false,
verifyHubSuccess: false,
@@ -408,9 +468,10 @@ class Settings extends React.Component {
verifyCameraSuccess: false,
verifyCameraError: false,
verifyCameraErrorMessage: '',
verifyOnvifSuccess: false,
verifyOnvifError: false,
hubSuccess: false,
hubError: false,
loadingCamera: true,
});
dispatchVerifyCamera(
@@ -451,6 +512,10 @@ class Settings extends React.Component {
verifyCameraSuccess,
verifyCameraError,
verifyCameraErrorMessage,
loadingOnvif,
verifyOnvifSuccess,
verifyOnvifError,
verifyOnvifErrorMessage,
loadingCamera,
loading,
loadingHub,
@@ -650,10 +715,23 @@ class Settings extends React.Component {
type="alert"
message={`${t(
'settings.info.verify_camera_error'
)} :${verifyCameraErrorMessage}`}
)}: ${verifyCameraErrorMessage}`}
/>
)}
{loadingOnvif && (
<InfoBar type="loading" message={t('settings.info.verify_onvif')} />
)}
{verifyOnvifSuccess && (
<InfoBar
type="success"
message={t('settings.info.verify_onvif_success')}
/>
)}
{verifyOnvifError && (
<InfoBar type="alert" message={`${verifyOnvifErrorMessage}`} />
)}
{loadingHub && (
<InfoBar type="loading" message={t('settings.info.verify_hub')} />
)}
@@ -1101,7 +1179,7 @@ class Settings extends React.Component {
noPadding
label={t('settings.camera.onvif_xaddr')}
value={config.capture.ipcamera.onvif_xaddr}
placeholder="http://x.x.x.x/onvif/device_service"
placeholder="x.x.x.x:yyyy"
onChange={(value) =>
this.onUpdateField(
'capture.ipcamera',
@@ -1141,6 +1219,12 @@ class Settings extends React.Component {
/>
</BlockBody>
<BlockFooter>
<Button
label={t('buttons.verify_connection')}
type="default"
icon="verify"
onClick={this.verifyONVIF}
/>
<Button
label={t('buttons.save')}
type="default"
@@ -2243,6 +2327,8 @@ const mapStateToProps = (state /* , ownProps */) => ({
});
const mapDispatchToProps = (dispatch /* , ownProps */) => ({
dispatchVerifyOnvif: (config, success, error) =>
dispatch(verifyOnvif(config, success, error)),
dispatchVerifyCamera: (streamType, config, success, error) =>
dispatch(verifyCamera(streamType, config, success, error)),
dispatchVerifyHub: (config, success, error) =>
@@ -2270,6 +2356,7 @@ Settings.propTypes = {
dispatchUpdateRegion: PropTypes.func.isRequired,
dispatchRemoveRegion: PropTypes.func.isRequired,
dispatchVerifyCamera: PropTypes.func.isRequired,
dispatchVerifyOnvif: PropTypes.func.isRequired,
};
export default withTranslation()(