Compare commits

...

30 Commits

Author SHA1 Message Date
Cedric Verstraeten
bf97bd72f1 add osusergo 2023-06-28 08:41:51 +02:00
Cedric Verstraeten
4b8b6bf66a fix balena links 2023-06-26 08:22:12 +02:00
Cedric Verstraeten
4b6c25bb85 documentation for balena apps 2023-06-25 23:25:42 +02:00
Cedric Verstraeten
729b38999e add deploy with balena 2023-06-25 22:14:23 +02:00
Cedric Verstraeten
4cbf0323f1 Reference separate balena repositories 2023-06-25 20:21:44 +02:00
Cedric Verstraeten
1f5cb8ca88 merge directories 2023-06-25 20:05:03 +02:00
Cedric Verstraeten
8be0a04502 add balena deployment (app + block) 2023-06-25 20:03:37 +02:00
Cedric Verstraeten
bdc0039a24 fix: might be empty if not set, so will never fire motion alert 2023-06-24 20:22:51 +02:00
Cedric Verstraeten
756b893ecd reference latest tags 2023-06-24 12:58:16 +02:00
Cedric Verstraeten
36323b076f Fix for Kerberos Vault persistence check 2023-06-23 21:13:13 +02:00
Cedric Verstraeten
95f43b6444 Fix for empty vault settings, throw error 2023-06-23 20:20:38 +02:00
Cedric Verstraeten
5c23a62ac3 New function to validate Kerberos Hub connectivity and subscription 2023-06-23 19:01:04 +02:00
Cedric Verstraeten
2b425a2ddd add test video for verification 2023-06-23 17:16:13 +02:00
Cedric Verstraeten
abeeb95204 make region editable through ENV + add new upload function to upload to Kerberos Hub 2023-06-23 16:14:01 +02:00
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
54 changed files with 1440 additions and 571 deletions

View File

@@ -32,7 +32,7 @@ RUN cat /go/src/github.com/kerberos-io/agent/machinery/version
RUN cd /go/src/github.com/kerberos-io/agent/machinery && \
go mod download && \
go build -tags timetzdata,netgo --ldflags '-s -w -extldflags "-static -latomic"' main.go && \
go build -tags timetzdata,netgo,osusergo --ldflags '-s -w -extldflags "-static -latomic"' main.go && \
mkdir -p /agent && \
mv main /agent && \
mv version /agent && \
@@ -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

@@ -78,12 +78,9 @@ If you want to connect to an USB or Raspberry Pi camera, [you'll need to run our
## Quickstart - Balena
Run Kerberos Agent with Balena super powers. Monitor your agent with seamless remote access, and an encrypted https endpoint.
Checkout our fleet on [Balena Hub](https://hub.balena.io/fleets?0%5B0%5D%5Bn%5D=any&0%5B0%5D%5Bo%5D=full_text_search&0%5B0%5D%5Bv%5D=agent), and add your agent.
Run Kerberos Agent with [Balena Cloud](https://www.balena.io/) super powers. Monitor your Kerberos Agent with seamless remote access, over the air updates, an encrypted public `https` endpoint and many more. Checkout our application `video-surveillance` on [Balena Hub](https://hub.balena.io/apps/2064752/video-surveillance), and create your first or fleet of Kerberos Agent(s).
[![balena deploy button](https://www.balena.io/deploy.svg)](https://dashboard.balena-cloud.com/deploy?repoUrl=https://github.com/kerberos-io/agent)
**_Work In Progress_** - Currently we only support IP and USB Cameras, we have [an approach for leveraging the Raspberry Pi camera](https://github.com/kerberos-io/camera-to-rtsp), but this isn't working as expected with Balena. If you require this, you'll need to use the traditional Docker deployment with sidecar as mentioned above.
[![deploy with balena](https://balena.io/deploy.svg)](https://dashboard.balena-cloud.com/deploy?repoUrl=https://github.com/kerberos-io/balena-agent)
## A world of Kerberos Agents
@@ -122,6 +119,7 @@ We have documented the different deployment models [in the `deployments` directo
- [Red Hat OpenShift with Ansible](https://github.com/kerberos-io/agent/tree/master/deployments#4-red-hat-ansible-and-openshift)
- [Terraform](https://github.com/kerberos-io/agent/tree/master/deployments#5-terraform)
- [Salt](https://github.com/kerberos-io/agent/tree/master/deployments#6-salt)
- [Balena](https://github.com/kerberos-io/agent/tree/master/deployments#8-balena)
By default your Kerberos Agents will store all its configuration and recordings inside the container. To help you automate and have a more consistent data governance, you can attach volumes to configure and persist data of your Kerberos Agents, and/or configure each Kerberos Agent through environment variables.
@@ -165,6 +163,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. | "" |
@@ -202,7 +201,7 @@ Next to attaching the configuration file, it is also possible to override the co
| `AGENT_HUB_URI` | The Kerberos Hub API, defaults to our Kerberos Hub SAAS. | "https://api.hub.domain.com" |
| `AGENT_HUB_KEY` | The access key linked to your account in Kerberos Hub. | "" |
| `AGENT_HUB_PRIVATE_KEY` | The secret access key linked to your account in Kerberos Hub. | "" |
| `AGENT_HUB_USERNAME` | Your Kerberos Hub username, which owns the above access and secret keys. | "" |
| `AGENT_HUB_REGION` | The Kerberos Hub region, to which you want to upload. | "" |
| `AGENT_HUB_SITE` | The site ID of a site you've created in your Kerberos Hub account. | "" |
| `AGENT_KERBEROSVAULT_URI` | The Kerberos Vault API url. | "https://vault.domain.com/api" |
| `AGENT_KERBEROSVAULT_ACCESS_KEY` | The access key of a Kerberos Vault account. | "" |

View File

@@ -14,6 +14,7 @@ We will discuss following deployment models.
- [5. Kerberos Factory](#5-kerberos-factory)
- [6. Terraform](#6-terraform)
- [7. Salt](#7-salt)
- [8. Balena](#8-balena)
## 0. Static binary
@@ -58,3 +59,11 @@ To be written
## 7. Salt
To be written
## 8. Balena
Balena Cloud provide a seamless way of building and deploying applications at scale through the conceps of `blocks`, `apps` and `fleets`. Once you have your `app` deployed, for example our Kerberos Agent, you can benefit from features such as: remote access, over the air updates, an encrypted public `https` endpoint and many more.
Together with the Balena.io team we've build a Balena App, called [`video-surveillance`](https://hub.balena.io/apps/2064752/video-surveillance), which any can use to deploy a video surveillance system in a matter of minutes with all the expected management features you can think of.
> Learn more [about Kerberos Agent with Balena](https://github.com/kerberos-io/agent/tree/master/deployments/balena).

View File

@@ -82,7 +82,7 @@
initContainers:
- name: download-config
image: kerberos/agent:1b96d01
image: kerberos/agent:latest
volumeMounts:
- name: kerberos-data
mountPath: /home/agent/data/config
@@ -96,7 +96,7 @@
containers:
- name: agent
image: kerberos/agent:1b96d01
image: kerberos/agent:latest
volumeMounts:
- name: kerberos-data
mountPath: /home/agent/data/config

View File

@@ -0,0 +1,31 @@
# Deployment with Balena
Balena Cloud provide a seamless way of building and deploying applications at scale through the conceps of `blocks`, `apps` and `fleets`. Once you have your `app` deployed, for example our Kerberos Agent, you can benefit from features such as: remote access, over the air updates, an encrypted public `https` endpoint and many more.
We provide two mechanisms to deploy Kerberos Agent to a Balena Cloud fleet:
1. Use Kerberos Agent as [a block part of your application](https://github.com/kerberos-io/balena-agent-block).
2. Use Kerberos Agent as [a stand-alone application](https://github.com/kerberos-io/balena-agent).
## Block
Within Balena you can build the concept of a block, which is the equivalent of container image or a function in a typical programming language. The idea of blocks, you can find a more thorough explanation [here](https://docs.balena.io/learn/develop/blocks/), is that you can compose and combine multiple `blocks` to level up to the concept an `app`.
You as a developer can choose which `blocks` you would like to use, to build the desired `application` state you prefer. For example you can use the [Kerberos Agent block](https://hub.balena.io/blocks/2064662/agent) to compose a video surveillance system as part of your existing set of blocks.
You can the `Kerberos Agent` block by defining following elements in your `compose` file.
agent:
image: bh.cr/kerberos_io/agent
## App
Next to building individual `blocks` you as a developer can also decide to build up an application, composed of one or more `blocks` or third-party containers, and publish it as an `app` to the Balena Hub. This is exactly [what we've done..](https://hub.balena.io/apps/2064752/video-surveillance)
On Balena Hub we have created the []`video-surveillance` application](https://hub.balena.io/apps/2064752/video-surveillance) that utilises the [Kerberos Agent `block`](https://hub.balena.io/blocks/2064662/agent). The idea of this application is that utilises the foundation of our Kerberos Agent, but that it might include more `blocks` over time to increase and improve functionalities from other community projects.
To deploy the application you can simply press below `Deploy button` or you can navigate to the [Balena Hub apps page](https://hub.balena.io/apps/2064752/video-surveillance).
[![deploy with balena](https://balena.io/deploy.svg)](https://dashboard.balena-cloud.com/deploy?repoUrl=https://github.com/kerberos-io/agent)
You can find the source code, `balena.yaml` and `docker-compose.yaml` files in the [`balena-agent` repository](https://github.com/kerberos-io/balena-agent).

View File

@@ -21,7 +21,7 @@ spec:
initContainers:
- name: download-config
image: kerberos/agent:1b96d01
image: kerberos/agent:latest
volumeMounts:
- name: kerberos-data
mountPath: /home/agent/data/config

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": ""
}
}

Binary file not shown.

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,6 +2,9 @@ module github.com/kerberos-io/agent/machinery
go 1.19
// 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
github.com/appleboy/gin-jwt/v2 v2.9.1
@@ -21,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

View File

@@ -264,8 +264,8 @@ 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=

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

@@ -2,7 +2,6 @@ package cloud
import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
@@ -15,14 +14,12 @@ import (
"github.com/gin-gonic/gin"
"github.com/golang-module/carbon/v2"
"github.com/kerberos-io/joy4/av/pubsub"
"github.com/minio/minio-go/v6"
mqtt "github.com/eclipse/paho.mqtt.golang"
av "github.com/kerberos-io/joy4/av"
"github.com/kerberos-io/joy4/cgo/ffmpeg"
"net/http"
"net/url"
"strconv"
"time"
@@ -85,9 +82,9 @@ func HandleUpload(configuration *models.Configuration, communication *models.Com
uploaded := false
configured := false
err = nil
if config.Cloud == "s3" {
uploaded, configured, err = UploadS3(configuration, fileName)
} else if config.Cloud == "kstorage" {
if config.Cloud == "s3" || config.Cloud == "kerberoshub" {
uploaded, configured, err = UploadKerberosHub(configuration, fileName)
} else if config.Cloud == "kstorage" || config.Cloud == "kerberosvault" {
uploaded, configured, err = UploadKerberosVault(configuration, fileName)
} else if config.Cloud == "dropbox" {
uploaded, configured, err = UploadDropbox(configuration, fileName)
@@ -103,6 +100,13 @@ func HandleUpload(configuration *models.Configuration, communication *models.Com
// Todo: implement ftp upload
} else if config.Cloud == "sftp" {
// Todo: implement sftp upload
} else if config.Cloud == "aws" {
// Todo: need to be updated, was previously used for hub.
uploaded, configured, err = UploadS3(configuration, fileName)
} else if config.Cloud == "azure" {
// Todo: implement azure upload
} else if config.Cloud == "google" {
// Todo: implement google upload
}
// And so on... (have a look here -> https://github.com/kerberos-io/agent/issues/95)
@@ -272,13 +276,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 +336,8 @@ loop:
"boot_time" : "%s",
"siteID" : "%s",
"onvif" : "%s",
"onvif_zoom" : "%s",
"onvif_pantilt" : "%s",
"cameraConnected": "%s",
"numberoffiles" : "33",
"timestamp" : 1564747908,
@@ -331,7 +345,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 +361,9 @@ loop:
communication.CloudTimestamp.Store(time.Now().Unix())
log.Log.Info("HandleHeartBeat: (200) Heartbeat received by Kerberos Hub.")
} else {
if communication.CloudTimestamp != nil && communication.CloudTimestamp.Load() != nil {
communication.CloudTimestamp.Store(int64(0))
}
log.Log.Error("HandleHeartBeat: (400) Something went wrong while sending to Kerberos Hub.")
}
@@ -525,14 +542,14 @@ func VerifyHub(c *gin.Context) {
err := c.BindJSON(&config)
if err == nil {
hubKey := config.HubKey
hubURI := config.HubURI
publicKey := config.HubKey
privateKey := config.HubPrivateKey
content := []byte(`{"message": "fake-message"}`)
body := bytes.NewReader(content)
req, err := http.NewRequest("POST", hubURI+"/queue/test", body)
req, err := http.NewRequest("POST", hubURI+"/subscription/verify", nil)
if err == nil {
req.Header.Set("X-Kerberos-Cloud-Key", hubKey)
req.Header.Set("X-Kerberos-Hub-PublicKey", publicKey)
req.Header.Set("X-Kerberos-Hub-PrivateKey", privateKey)
client := &http.Client{}
resp, err := client.Do(req)
@@ -589,88 +606,80 @@ func VerifyPersistence(c *gin.Context) {
if config.Cloud == "dropbox" {
VerifyDropbox(config, c)
} else if config.Cloud == "s3" {
} else if config.Cloud == "s3" || config.Cloud == "kerberoshub" {
// timestamp_microseconds_instanceName_regionCoordinates_numberOfChanges_token
// 1564859471_6-474162_oprit_577-283-727-375_1153_27.mp4
// - Timestamp
// - Size + - + microseconds
// - device
// - Region
// - Number of changes
// - Token
aws_access_key_id := config.S3.Publickey
aws_secret_access_key := config.S3.Secretkey
aws_region := config.S3.Region
// This is the new way ;)
if config.HubKey != "" {
aws_access_key_id = config.HubKey
}
if config.HubPrivateKey != "" {
aws_secret_access_key = config.HubPrivateKey
}
s3Client, err := minio.NewWithRegion("s3.amazonaws.com", aws_access_key_id, aws_secret_access_key, true, aws_region)
if err != nil {
if config.HubURI == "" ||
config.HubKey == "" ||
config.HubPrivateKey == "" ||
config.S3.Region == "" {
msg := "VerifyPersistence: Kerberos Hub not properly configured."
log.Log.Info(msg)
c.JSON(400, models.APIResponse{
Data: "Creation of Kerberos Hub connection failed: " + err.Error(),
Data: msg,
})
} else {
// Check if we need to use the proxy.
if config.S3.ProxyURI != "" {
var transport http.RoundTripper = &http.Transport{
Proxy: func(*http.Request) (*url.URL, error) {
return url.Parse(config.S3.ProxyURI)
},
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
s3Client.SetCustomTransport(transport)
// Open test-480p.mp4
file, err := os.Open("./data/test-480p.mp4")
if err != nil {
msg := "VerifyPersistence: error reading test-480p.mp4: " + err.Error()
log.Log.Error(msg)
c.JSON(400, models.APIResponse{
Data: msg,
})
}
defer file.Close()
req, err := http.NewRequest("POST", config.HubURI+"/storage/upload", file)
if err != nil {
msg := "VerifyPersistence: error reading Kerberos Hub HEAD request, " + config.HubURI + "/storage: " + err.Error()
log.Log.Error(msg)
c.JSON(400, models.APIResponse{
Data: msg,
})
}
deviceKey := "fake-key"
devicename := "justatest"
coordinates := "200-200-400-400"
eventToken := "769"
timestamp := time.Now().Unix()
fileName := strconv.FormatInt(timestamp, 10) + "_6-967003_justatest_200-200-400-400_24_769.mp4"
content := []byte("test-file")
body := bytes.NewReader(content)
fileName := strconv.FormatInt(timestamp, 10) +
"_6-967003_" + config.Name + "_200-200-400-400_24_769.mp4"
req.Header.Set("X-Kerberos-Storage-FileName", fileName)
req.Header.Set("X-Kerberos-Storage-Capture", "IPCamera")
req.Header.Set("X-Kerberos-Storage-Device", config.Key)
req.Header.Set("X-Kerberos-Hub-PublicKey", config.HubKey)
req.Header.Set("X-Kerberos-Hub-PrivateKey", config.HubPrivateKey)
req.Header.Set("X-Kerberos-Hub-Region", config.S3.Region)
n, err := s3Client.PutObject(config.S3.Bucket,
config.S3.Username+"/"+fileName,
body,
body.Size(),
minio.PutObjectOptions{
ContentType: "video/mp4",
StorageClass: "ONEZONE_IA",
UserMetadata: map[string]string{
"event-timestamp": strconv.FormatInt(timestamp, 10),
"event-microseconds": deviceKey,
"event-instancename": devicename,
"event-regioncoordinates": coordinates,
"event-numberofchanges": deviceKey,
"event-token": eventToken,
"productid": deviceKey,
"publickey": aws_access_key_id,
"uploadtime": "now",
},
})
client := &http.Client{}
if err != nil {
c.JSON(400, models.APIResponse{
Data: "Upload of fake recording failed: " + err.Error(),
})
resp, err := client.Do(req)
if resp != nil {
defer resp.Body.Close()
}
if err == nil && resp != nil {
if resp.StatusCode == 200 {
msg := "VerifyPersistence: Upload allowed using the credentials provided (" + config.HubKey + ", " + config.HubPrivateKey + ")"
log.Log.Info(msg)
c.JSON(200, models.APIResponse{
Data: msg,
})
} else {
msg := "VerifyPersistence: Upload NOT allowed using the credentials provided (" + config.HubKey + ", " + config.HubPrivateKey + ")"
log.Log.Info(msg)
c.JSON(400, models.APIResponse{
Data: msg,
})
}
} else {
c.JSON(200, models.APIResponse{
Data: "Upload Finished: file has been uploaded to bucket: " + strconv.FormatInt(n, 10),
msg := "VerifyPersistence: Error creating Kerberos Hub request"
log.Log.Info(msg)
c.JSON(400, models.APIResponse{
Data: msg,
})
}
}
} else if config.Cloud == "kstorage" {
} else if config.Cloud == "kstorage" || config.Cloud == "kerberosvault" {
uri := config.KStorage.URI
accessKey := config.KStorage.AccessKey
@@ -679,10 +688,9 @@ func VerifyPersistence(c *gin.Context) {
provider := config.KStorage.Provider
if err == nil && uri != "" && accessKey != "" && secretAccessKey != "" {
var postData = []byte(`{"title":"Buy cheese and bread for breakfast."}`)
client := &http.Client{}
req, err := http.NewRequest("POST", uri+"/ping", bytes.NewReader(postData))
client := &http.Client{}
req, err := http.NewRequest("POST", uri+"/ping", nil)
req.Header.Add("X-Kerberos-Storage-AccessKey", accessKey)
req.Header.Add("X-Kerberos-Storage-SecretAccessKey", secretAccessKey)
resp, err := client.Do(req)
@@ -694,32 +702,35 @@ func VerifyPersistence(c *gin.Context) {
if provider != "" || directory != "" {
hubKey := config.KStorage.CloudKey
// This is the new way ;)
if config.HubKey != "" {
hubKey = config.HubKey
}
// Generate a random name.
timestamp := time.Now().Unix()
fileName := strconv.FormatInt(timestamp, 10) +
"_6-967003_justatest_200-200-400-400_24_769.mp4"
content := []byte("test-file")
body := bytes.NewReader(content)
//fileSize := int64(len(content))
"_6-967003_" + config.Name + "_200-200-400-400_24_769.mp4"
req, err := http.NewRequest("POST", uri+"/storage", body)
// Open test-480p.mp4
file, err := os.Open("./data/test-480p.mp4")
if err != nil {
msg := "VerifyPersistence: error reading test-480p.mp4: " + err.Error()
log.Log.Error(msg)
c.JSON(400, models.APIResponse{
Data: msg,
})
}
defer file.Close()
req, err := http.NewRequest("POST", uri+"/storage", file)
if err == nil {
req.Header.Set("Content-Type", "video/mp4")
req.Header.Set("X-Kerberos-Storage-CloudKey", hubKey)
req.Header.Set("X-Kerberos-Storage-CloudKey", config.HubKey)
req.Header.Set("X-Kerberos-Storage-AccessKey", accessKey)
req.Header.Set("X-Kerberos-Storage-SecretAccessKey", secretAccessKey)
req.Header.Set("X-Kerberos-Storage-Provider", provider)
req.Header.Set("X-Kerberos-Storage-FileName", fileName)
req.Header.Set("X-Kerberos-Storage-Device", "test")
req.Header.Set("X-Kerberos-Storage-Device", config.Key)
req.Header.Set("X-Kerberos-Storage-Capture", "IPCamera")
req.Header.Set("X-Kerberos-Storage-Directory", directory)
client := &http.Client{}
resp, err := client.Do(req)
@@ -733,41 +744,45 @@ func VerifyPersistence(c *gin.Context) {
c.JSON(200, body)
} else {
c.JSON(400, models.APIResponse{
Data: "Something went wrong while verifying your persistence settings. Make sure your provider is the same as the storage provider in your Kerberos Vault, and the relevant storage provider is configured properly.",
Data: "VerifyPersistence: Something went wrong while verifying your persistence settings. Make sure your provider is the same as the storage provider in your Kerberos Vault, and the relevant storage provider is configured properly.",
})
}
}
}
} else {
c.JSON(400, models.APIResponse{
Data: "Upload of fake recording failed: " + err.Error(),
Data: "VerifyPersistence: Upload of fake recording failed: " + err.Error(),
})
}
} else {
c.JSON(400, models.APIResponse{
Data: "Something went wrong while creating /storage POST request." + err.Error(),
Data: "VerifyPersistence: Something went wrong while creating /storage POST request." + err.Error(),
})
}
} else {
c.JSON(400, models.APIResponse{
Data: "Provider and/or directory is missing from the request.",
Data: "VerifyPersistence: Provider and/or directory is missing from the request.",
})
}
} else {
c.JSON(400, models.APIResponse{
Data: "Something went wrong while verifying storage credentials: " + string(body),
Data: "VerifyPersistence: Something went wrong while verifying storage credentials: " + string(body),
})
}
} else {
c.JSON(400, models.APIResponse{
Data: "Something went wrong while verifying storage credentials:" + err.Error(),
Data: "VerifyPersistence: Something went wrong while verifying storage credentials:" + err.Error(),
})
}
} else {
c.JSON(400, models.APIResponse{
Data: "VerifyPersistence: please fill-in the required Kerberos Vault credentials.",
})
}
}
} else {
c.JSON(400, models.APIResponse{
Data: "No persistence was specified, so do not know what to verify:" + err.Error(),
Data: "VerifyPersistence: No persistence was specified, so do not know what to verify:" + err.Error(),
})
}
}

View File

@@ -0,0 +1,125 @@
package cloud
import (
"errors"
"io/ioutil"
"net/http"
"os"
"github.com/kerberos-io/agent/machinery/src/log"
"github.com/kerberos-io/agent/machinery/src/models"
)
func UploadKerberosHub(configuration *models.Configuration, fileName string) (bool, bool, error) {
config := configuration.Config
if config.HubURI == "" ||
config.HubKey == "" ||
config.HubPrivateKey == "" ||
config.S3.Region == "" {
err := "UploadKerberosHub: Kerberos Hub not properly configured."
log.Log.Info(err)
return false, false, errors.New(err)
}
// timestamp_microseconds_instanceName_regionCoordinates_numberOfChanges_token
// 1564859471_6-474162_oprit_577-283-727-375_1153_27.mp4
// - Timestamp
// - Size + - + microseconds
// - device
// - Region
// - Number of changes
// - Token
log.Log.Info("UploadKerberosHub: Uploading to Kerberos Hub (" + config.HubURI + ")")
log.Log.Info("UploadKerberosHub: Upload started for " + fileName)
fullname := "data/recordings/" + fileName
// Check if we still have the file otherwise we abort the request.
file, err := os.OpenFile(fullname, os.O_RDWR, 0755)
if file != nil {
defer file.Close()
}
if err != nil {
err := "UploadKerberosHub: Upload Failed, file doesn't exists anymore."
log.Log.Info(err)
return false, true, errors.New(err)
}
// Check if we are allowed to upload to the hub with these credentials.
// There might be different reasons like (muted, read-only..)
req, err := http.NewRequest("HEAD", config.HubURI+"/storage/upload", nil)
if err != nil {
errorMessage := "UploadKerberosHub: error reading HEAD request, " + config.HubURI + "/storage: " + err.Error()
log.Log.Error(errorMessage)
return false, true, errors.New(errorMessage)
}
req.Header.Set("X-Kerberos-Storage-FileName", fileName)
req.Header.Set("X-Kerberos-Storage-Capture", "IPCamera")
req.Header.Set("X-Kerberos-Storage-Device", config.Key)
req.Header.Set("X-Kerberos-Hub-PublicKey", config.HubKey)
req.Header.Set("X-Kerberos-Hub-PrivateKey", config.HubPrivateKey)
req.Header.Set("X-Kerberos-Hub-Region", config.S3.Region)
client := &http.Client{}
resp, err := client.Do(req)
if resp != nil {
defer resp.Body.Close()
}
if err == nil {
if resp != nil {
if err == nil {
if resp.StatusCode == 200 {
log.Log.Info("UploadKerberosHub: Upload allowed using the credentials provided (" + config.HubKey + ", " + config.HubPrivateKey + ")")
} else {
log.Log.Info("UploadKerberosHub: Upload NOT allowed using the credentials provided (" + config.HubKey + ", " + config.HubPrivateKey + ")")
return false, true, nil
}
}
}
}
// Now we know we are allowed to upload to the hub, we can start uploading.
req, err = http.NewRequest("POST", config.HubURI+"/storage/upload", file)
if err != nil {
errorMessage := "UploadKerberosHub: error reading POST request, " + config.KStorage.URI + "/storage/upload: " + err.Error()
log.Log.Error(errorMessage)
return false, true, errors.New(errorMessage)
}
req.Header.Set("Content-Type", "video/mp4")
req.Header.Set("X-Kerberos-Storage-FileName", fileName)
req.Header.Set("X-Kerberos-Storage-Capture", "IPCamera")
req.Header.Set("X-Kerberos-Storage-Device", config.Key)
req.Header.Set("X-Kerberos-Hub-PublicKey", config.HubKey)
req.Header.Set("X-Kerberos-Hub-PrivateKey", config.HubPrivateKey)
req.Header.Set("X-Kerberos-Hub-Region", config.S3.Region)
client = &http.Client{}
resp, err = client.Do(req)
if resp != nil {
defer resp.Body.Close()
}
if err == nil {
if resp != nil {
body, err := ioutil.ReadAll(resp.Body)
if err == nil {
if resp.StatusCode == 200 {
log.Log.Info("UploadKerberosHub: Upload Finished, " + resp.Status + ".")
return true, true, nil
} else {
log.Log.Info("UploadKerberosHub: Upload Failed, " + resp.Status + ", " + string(body))
return false, true, nil
}
}
}
}
errorMessage := "UploadKerberosHub: Upload Failed, " + err.Error()
log.Log.Info(errorMessage)
return false, true, errors.New(errorMessage)
}

View File

@@ -83,17 +83,25 @@ func OpenConfig(configuration *models.Configuration) {
db := client.Database(database.DatabaseName)
collection := db.Collection("configuration")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
collection.FindOne(ctx, bson.M{
var globalConfig models.Config
err := collection.FindOne(context.Background(), bson.M{
"type": "global",
}).Decode(&configuration.GlobalConfig)
}).Decode(&globalConfig)
if err != nil {
log.Log.Error("Could not find global configuration, using default configuration.")
}
configuration.GlobalConfig = globalConfig
collection.FindOne(ctx, 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"),
}).Decode(&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.
@@ -209,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
@@ -394,12 +401,12 @@ func OverrideWithEnvironmentVariables(configuration *models.Configuration) {
case "AGENT_HUB_PRIVATE_KEY":
configuration.Config.HubPrivateKey = value
break
case "AGENT_HUB_USERNAME":
configuration.Config.S3.Username = value
break
case "AGENT_HUB_SITE":
configuration.Config.HubSite = value
break
case "AGENT_HUB_REGION":
configuration.Config.S3.Region = value
break
/* When storing in a Kerberos Vault */
case "AGENT_KERBEROSVAULT_URI":
@@ -440,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()

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"
)
@@ -142,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.")
}
}
}
}
@@ -166,7 +165,7 @@ func ProcessMotion(motionCursor *pubsub.QueueCursor, configuration *models.Confi
if detectMotion && isPixelChangeThresholdReached {
// If offline mode is disabled, send a message to the hub
if config.Offline == "false" {
if config.Offline != "true" {
if mqttClient != nil {
if key != "" {
mqttClient.Publish("kerberos/"+key+"/device/"+config.Key+"/motion", 2, false, "motion")

View File

@@ -28,10 +28,10 @@ func New() *mongo.Client {
password := os.Getenv("MONGODB_PASSWORD")
authentication := "SCRAM-SHA-256"
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_init_ctx.Do(func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_instance = new(DB)
mongodbURI := fmt.Sprintf("mongodb://%s:%s@%s", username, password, host)
if replicaset != "" {

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"
@@ -2030,17 +2114,6 @@ class Settings extends React.Component {
/>
{config.cloud === this.KERBEROS_HUB && (
<>
<Input
noPadding
label={t('settings.persistence.kerberoshub_proxyurl')}
placeholder={t(
'settings.persistence.kerberoshub_description_proxyurl'
)}
value={config.s3 ? config.s3.proxyuri : ''}
onChange={(value) =>
this.onUpdateField('s3', 'proxyuri', value, config.s3)
}
/>
<Input
noPadding
label={t('settings.persistence.kerberoshub_region')}
@@ -2052,28 +2125,6 @@ class Settings extends React.Component {
this.onUpdateField('s3', 'region', value, config.s3)
}
/>
<Input
noPadding
label={t('settings.persistence.kerberoshub_bucket')}
placeholder={t(
'settings.persistence.kerberoshub_description_bucket'
)}
value={config.s3 ? config.s3.bucket : ''}
onChange={(value) =>
this.onUpdateField('s3', 'bucket', value, config.s3)
}
/>
<Input
noPadding
label={t('settings.persistence.kerberoshub_username')}
placeholder={t(
'settings.persistence.kerberoshub_description_username'
)}
value={config.s3 ? config.s3.username : ''}
onChange={(value) =>
this.onUpdateField('s3', 'username', value, config.s3)
}
/>
</>
)}
{config.cloud === this.KERBEROS_VAULT && (
@@ -2243,6 +2294,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 +2323,7 @@ Settings.propTypes = {
dispatchUpdateRegion: PropTypes.func.isRequired,
dispatchRemoveRegion: PropTypes.func.isRequired,
dispatchVerifyCamera: PropTypes.func.isRequired,
dispatchVerifyOnvif: PropTypes.func.isRequired,
};
export default withTranslation()(