Compare commits

...

11 Commits

Author SHA1 Message Date
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
40 changed files with 1056 additions and 303 deletions

View File

@@ -122,6 +122,7 @@ RUN cp /home/agent/data/config/config.json /home/agent/data/config.template.json
# Set permissions correctly
RUN chown -R agent:kerberosio /home/agent/data
RUN chown -R agent:kerberosio /home/agent/www
###########################
# Grant the necessary root capabilities to the process trying to bind to the privileged port

View File

@@ -165,6 +165,7 @@ Next to attaching the configuration file, it is also possible to override the co
| Name | Description | Default Value |
| --------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------ |
| `AGENT_MODE` | You can choose to run this in 'release' for production, and or 'demo' for showcasing. | "release" |
| `AGENT_USERNAME` | The username used to authenticate against the Kerberos Agent login page. | "root" |
| `AGENT_PASSWORD` | The password used to authenticate against the Kerberos Agent login page. | "root" |
| `AGENT_KEY` | A unique identifier for your Kerberos Agent, this is auto-generated but can be overriden. | "" |
@@ -202,7 +203,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

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

View File

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

View File

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

View File

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

View File

@@ -85,9 +85,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 +103,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 +279,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 +339,8 @@ loop:
"boot_time" : "%s",
"siteID" : "%s",
"onvif" : "%s",
"onvif_zoom" : "%s",
"onvif_pantilt" : "%s",
"cameraConnected": "%s",
"numberoffiles" : "33",
"timestamp" : 1564747908,
@@ -331,7 +348,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 +364,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.")
}

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

@@ -401,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":

View File

@@ -85,14 +85,12 @@ func Bootstrap(configuration *models.Configuration, communication *models.Commun
break
}
// We will reconfigure or restart the agent, we will mark the agent as not connected.
communication.CameraConnected = false
// 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) {
@@ -224,9 +222,6 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
subQueue.WriteHeader(subStreams)
}
// If we reach this point, we have a working RTSP connection.
communication.CameraConnected = true
// Handle the camera stream
go capture.HandleStream(infile, queue, communication)
@@ -273,15 +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"
@@ -308,9 +315,6 @@ func RunAgent(configuration *models.Configuration, communication *models.Communi
close(communication.HandleMotion)
communication.HandleMotion = nil
// 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)

View File

@@ -139,7 +139,7 @@ func ProcessMotion(motionCursor *pubsub.QueueCursor, configuration *models.Confi
hour := now.Hour()
minute := now.Minute()
second := now.Second()
if config.Timetable != nil {
if config.Timetable != nil && len(config.Timetable) > 0 {
timeInterval := config.Timetable[int(weekday)]
if timeInterval != nil {
start1 := timeInterval.Start1

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

@@ -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

@@ -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() {
@@ -274,6 +279,8 @@ class Settings extends React.Component {
verifyHubError: false,
configSuccess: false,
configError: false,
verifyOnvifSuccess: false,
verifyOnvifError: false,
});
if (config) {
@@ -295,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) {
@@ -317,6 +370,8 @@ class Settings extends React.Component {
verifyCameraSuccess: false,
verifyCameraError: false,
verifyCameraErrorMessage: '',
verifyOnvifSuccess: false,
verifyOnvifError: false,
loadingHub: true,
});
@@ -362,6 +417,8 @@ class Settings extends React.Component {
persistenceError: false,
verifyCameraSuccess: false,
verifyCameraError: false,
verifyOnvifSuccess: false,
verifyOnvifError: false,
verifyCameraErrorMessage: '',
loading: true,
});
@@ -402,6 +459,7 @@ class Settings extends React.Component {
this.setState({
configSuccess: false,
configError: false,
loadingCamera: true,
verifyPersistenceSuccess: false,
verifyPersistenceError: false,
verifyHubSuccess: false,
@@ -410,9 +468,10 @@ class Settings extends React.Component {
verifyCameraSuccess: false,
verifyCameraError: false,
verifyCameraErrorMessage: '',
verifyOnvifSuccess: false,
verifyOnvifError: false,
hubSuccess: false,
hubError: false,
loadingCamera: true,
});
dispatchVerifyCamera(
@@ -453,6 +512,10 @@ class Settings extends React.Component {
verifyCameraSuccess,
verifyCameraError,
verifyCameraErrorMessage,
loadingOnvif,
verifyOnvifSuccess,
verifyOnvifError,
verifyOnvifErrorMessage,
loadingCamera,
loading,
loadingHub,
@@ -652,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')} />
)}
@@ -1103,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',
@@ -1143,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"
@@ -2245,6 +2327,8 @@ const mapStateToProps = (state /* , ownProps */) => ({
});
const mapDispatchToProps = (dispatch /* , ownProps */) => ({
dispatchVerifyOnvif: (config, success, error) =>
dispatch(verifyOnvif(config, success, error)),
dispatchVerifyCamera: (streamType, config, success, error) =>
dispatch(verifyCamera(streamType, config, success, error)),
dispatchVerifyHub: (config, success, error) =>
@@ -2272,6 +2356,7 @@ Settings.propTypes = {
dispatchUpdateRegion: PropTypes.func.isRequired,
dispatchRemoveRegion: PropTypes.func.isRequired,
dispatchVerifyCamera: PropTypes.func.isRequired,
dispatchVerifyOnvif: PropTypes.func.isRequired,
};
export default withTranslation()(