update dashboard page, add breadcrumb buttons

This commit is contained in:
Thomas Quandalle
2022-08-31 15:12:14 +02:00
parent cf40b68338
commit fb7ef7aa97
14 changed files with 1294 additions and 129 deletions

View File

@@ -24,7 +24,382 @@ const docTemplate = `{
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {},
"paths": {
"/api/hub/verify": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Will verify the hub connectivity.",
"tags": [
"config"
],
"summary": "Will verify the hub connectivity.",
"operationId": "verify-hub",
"parameters": [
{
"description": "Config",
"name": "config",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.Config"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.APIResponse"
}
}
}
}
},
"/api/persistence/verify": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Will verify the persistence.",
"tags": [
"config"
],
"summary": "Will verify the persistence.",
"operationId": "verify-persistence",
"parameters": [
{
"description": "Config",
"name": "config",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.Config"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.APIResponse"
}
}
}
}
}
},
"definitions": {
"models.APIResponse": {
"type": "object",
"properties": {
"data": {}
}
},
"models.Capture": {
"type": "object",
"properties": {
"continuous": {
"type": "string"
},
"forwardwebrtc": {
"type": "string"
},
"fragmented": {
"type": "string"
},
"fragmentedduration": {
"type": "integer"
},
"ipcamera": {
"$ref": "#/definitions/models.IPCamera"
},
"maxlengthrecording": {
"type": "integer"
},
"name": {
"type": "string"
},
"pixelChangeThreshold": {
"type": "integer"
},
"postrecording": {
"type": "integer"
},
"prerecording": {
"type": "integer"
},
"raspicamera": {
"$ref": "#/definitions/models.RaspiCamera"
},
"transcodingresolution": {
"type": "integer"
},
"transcodingwebrtc": {
"type": "string"
},
"usbcamera": {
"$ref": "#/definitions/models.USBCamera"
}
}
},
"models.Config": {
"type": "object",
"properties": {
"capture": {
"$ref": "#/definitions/models.Capture"
},
"cloud": {
"type": "string"
},
"condition_uri": {
"type": "string"
},
"heartbeaturi": {
"description": "obsolete",
"type": "string"
},
"hub_key": {
"type": "string"
},
"hub_private_key": {
"type": "string"
},
"hub_site": {
"type": "string"
},
"hub_uri": {
"type": "string"
},
"key": {
"type": "string"
},
"kstorage": {
"$ref": "#/definitions/models.KStorage"
},
"mqtt_password": {
"type": "string"
},
"mqtt_username": {
"type": "string"
},
"mqtturi": {
"type": "string"
},
"name": {
"type": "string"
},
"offline": {
"type": "string"
},
"region": {
"$ref": "#/definitions/models.Region"
},
"s3": {
"$ref": "#/definitions/models.S3"
},
"stunuri": {
"type": "string"
},
"time": {
"type": "string"
},
"timetable": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Timetable"
}
},
"timezone": {
"type": "string"
},
"turn_password": {
"type": "string"
},
"turn_username": {
"type": "string"
},
"turnuri": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"models.Coordinate": {
"type": "object",
"properties": {
"x": {
"type": "number"
},
"y": {
"type": "number"
}
}
},
"models.IPCamera": {
"type": "object",
"properties": {
"fps": {
"type": "string"
},
"onvif": {
"type": "boolean"
},
"onvif_password": {
"type": "string"
},
"onvif_username": {
"type": "string"
},
"onvif_xaddr": {
"type": "string"
},
"rtsp": {
"type": "string"
},
"sub_rtsp": {
"type": "string"
}
}
},
"models.KStorage": {
"type": "object",
"properties": {
"access_key": {
"type": "string"
},
"cloud_key": {
"type": "string"
},
"directory": {
"type": "string"
},
"provider": {
"type": "string"
},
"secret_access_key": {
"type": "string"
},
"uri": {
"type": "string"
}
}
},
"models.Polygon": {
"type": "object",
"properties": {
"coordinates": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Coordinate"
}
},
"id": {
"type": "string"
}
}
},
"models.RaspiCamera": {
"type": "object",
"properties": {
"device": {
"type": "string"
}
}
},
"models.Rectangle": {
"type": "object",
"properties": {
"x1": {
"type": "integer"
},
"x2": {
"type": "integer"
},
"y1": {
"type": "integer"
},
"y2": {
"type": "integer"
}
}
},
"models.Region": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"polygon": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Polygon"
}
},
"rectangle": {
"$ref": "#/definitions/models.Rectangle"
}
}
},
"models.S3": {
"type": "object",
"properties": {
"bucket": {
"type": "string"
},
"proxy": {
"type": "string"
},
"proxyuri": {
"type": "string"
},
"publickey": {
"type": "string"
},
"region": {
"type": "string"
},
"secretkey": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"models.Timetable": {
"type": "object",
"properties": {
"end1": {
"type": "integer"
},
"end2": {
"type": "integer"
},
"start1": {
"type": "integer"
},
"start2": {
"type": "integer"
}
}
},
"models.USBCamera": {
"type": "object",
"properties": {
"device": {
"type": "string"
}
}
}
},
"securityDefinitions": {
"Bearer": {
"type": "apiKey",

View File

@@ -16,7 +16,382 @@
"version": "1.0"
},
"basePath": "/",
"paths": {},
"paths": {
"/api/hub/verify": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Will verify the hub connectivity.",
"tags": [
"config"
],
"summary": "Will verify the hub connectivity.",
"operationId": "verify-hub",
"parameters": [
{
"description": "Config",
"name": "config",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.Config"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.APIResponse"
}
}
}
}
},
"/api/persistence/verify": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Will verify the persistence.",
"tags": [
"config"
],
"summary": "Will verify the persistence.",
"operationId": "verify-persistence",
"parameters": [
{
"description": "Config",
"name": "config",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.Config"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.APIResponse"
}
}
}
}
}
},
"definitions": {
"models.APIResponse": {
"type": "object",
"properties": {
"data": {}
}
},
"models.Capture": {
"type": "object",
"properties": {
"continuous": {
"type": "string"
},
"forwardwebrtc": {
"type": "string"
},
"fragmented": {
"type": "string"
},
"fragmentedduration": {
"type": "integer"
},
"ipcamera": {
"$ref": "#/definitions/models.IPCamera"
},
"maxlengthrecording": {
"type": "integer"
},
"name": {
"type": "string"
},
"pixelChangeThreshold": {
"type": "integer"
},
"postrecording": {
"type": "integer"
},
"prerecording": {
"type": "integer"
},
"raspicamera": {
"$ref": "#/definitions/models.RaspiCamera"
},
"transcodingresolution": {
"type": "integer"
},
"transcodingwebrtc": {
"type": "string"
},
"usbcamera": {
"$ref": "#/definitions/models.USBCamera"
}
}
},
"models.Config": {
"type": "object",
"properties": {
"capture": {
"$ref": "#/definitions/models.Capture"
},
"cloud": {
"type": "string"
},
"condition_uri": {
"type": "string"
},
"heartbeaturi": {
"description": "obsolete",
"type": "string"
},
"hub_key": {
"type": "string"
},
"hub_private_key": {
"type": "string"
},
"hub_site": {
"type": "string"
},
"hub_uri": {
"type": "string"
},
"key": {
"type": "string"
},
"kstorage": {
"$ref": "#/definitions/models.KStorage"
},
"mqtt_password": {
"type": "string"
},
"mqtt_username": {
"type": "string"
},
"mqtturi": {
"type": "string"
},
"name": {
"type": "string"
},
"offline": {
"type": "string"
},
"region": {
"$ref": "#/definitions/models.Region"
},
"s3": {
"$ref": "#/definitions/models.S3"
},
"stunuri": {
"type": "string"
},
"time": {
"type": "string"
},
"timetable": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Timetable"
}
},
"timezone": {
"type": "string"
},
"turn_password": {
"type": "string"
},
"turn_username": {
"type": "string"
},
"turnuri": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"models.Coordinate": {
"type": "object",
"properties": {
"x": {
"type": "number"
},
"y": {
"type": "number"
}
}
},
"models.IPCamera": {
"type": "object",
"properties": {
"fps": {
"type": "string"
},
"onvif": {
"type": "boolean"
},
"onvif_password": {
"type": "string"
},
"onvif_username": {
"type": "string"
},
"onvif_xaddr": {
"type": "string"
},
"rtsp": {
"type": "string"
},
"sub_rtsp": {
"type": "string"
}
}
},
"models.KStorage": {
"type": "object",
"properties": {
"access_key": {
"type": "string"
},
"cloud_key": {
"type": "string"
},
"directory": {
"type": "string"
},
"provider": {
"type": "string"
},
"secret_access_key": {
"type": "string"
},
"uri": {
"type": "string"
}
}
},
"models.Polygon": {
"type": "object",
"properties": {
"coordinates": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Coordinate"
}
},
"id": {
"type": "string"
}
}
},
"models.RaspiCamera": {
"type": "object",
"properties": {
"device": {
"type": "string"
}
}
},
"models.Rectangle": {
"type": "object",
"properties": {
"x1": {
"type": "integer"
},
"x2": {
"type": "integer"
},
"y1": {
"type": "integer"
},
"y2": {
"type": "integer"
}
}
},
"models.Region": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"polygon": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Polygon"
}
},
"rectangle": {
"$ref": "#/definitions/models.Rectangle"
}
}
},
"models.S3": {
"type": "object",
"properties": {
"bucket": {
"type": "string"
},
"proxy": {
"type": "string"
},
"proxyuri": {
"type": "string"
},
"publickey": {
"type": "string"
},
"region": {
"type": "string"
},
"secretkey": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"models.Timetable": {
"type": "object",
"properties": {
"end1": {
"type": "integer"
},
"end2": {
"type": "integer"
},
"start1": {
"type": "integer"
},
"start2": {
"type": "integer"
}
}
},
"models.USBCamera": {
"type": "object",
"properties": {
"device": {
"type": "string"
}
}
}
},
"securityDefinitions": {
"Bearer": {
"type": "apiKey",

View File

@@ -1,4 +1,204 @@
basePath: /
definitions:
models.APIResponse:
properties:
data: {}
type: object
models.Capture:
properties:
continuous:
type: string
forwardwebrtc:
type: string
fragmented:
type: string
fragmentedduration:
type: integer
ipcamera:
$ref: '#/definitions/models.IPCamera'
maxlengthrecording:
type: integer
name:
type: string
pixelChangeThreshold:
type: integer
postrecording:
type: integer
prerecording:
type: integer
raspicamera:
$ref: '#/definitions/models.RaspiCamera'
transcodingresolution:
type: integer
transcodingwebrtc:
type: string
usbcamera:
$ref: '#/definitions/models.USBCamera'
type: object
models.Config:
properties:
capture:
$ref: '#/definitions/models.Capture'
cloud:
type: string
condition_uri:
type: string
heartbeaturi:
description: obsolete
type: string
hub_key:
type: string
hub_private_key:
type: string
hub_site:
type: string
hub_uri:
type: string
key:
type: string
kstorage:
$ref: '#/definitions/models.KStorage'
mqtt_password:
type: string
mqtt_username:
type: string
mqtturi:
type: string
name:
type: string
offline:
type: string
region:
$ref: '#/definitions/models.Region'
s3:
$ref: '#/definitions/models.S3'
stunuri:
type: string
time:
type: string
timetable:
items:
$ref: '#/definitions/models.Timetable'
type: array
timezone:
type: string
turn_password:
type: string
turn_username:
type: string
turnuri:
type: string
type:
type: string
type: object
models.Coordinate:
properties:
x:
type: number
"y":
type: number
type: object
models.IPCamera:
properties:
fps:
type: string
onvif:
type: boolean
onvif_password:
type: string
onvif_username:
type: string
onvif_xaddr:
type: string
rtsp:
type: string
sub_rtsp:
type: string
type: object
models.KStorage:
properties:
access_key:
type: string
cloud_key:
type: string
directory:
type: string
provider:
type: string
secret_access_key:
type: string
uri:
type: string
type: object
models.Polygon:
properties:
coordinates:
items:
$ref: '#/definitions/models.Coordinate'
type: array
id:
type: string
type: object
models.RaspiCamera:
properties:
device:
type: string
type: object
models.Rectangle:
properties:
x1:
type: integer
x2:
type: integer
y1:
type: integer
y2:
type: integer
type: object
models.Region:
properties:
name:
type: string
polygon:
items:
$ref: '#/definitions/models.Polygon'
type: array
rectangle:
$ref: '#/definitions/models.Rectangle'
type: object
models.S3:
properties:
bucket:
type: string
proxy:
type: string
proxyuri:
type: string
publickey:
type: string
region:
type: string
secretkey:
type: string
username:
type: string
type: object
models.Timetable:
properties:
end1:
type: integer
end2:
type: integer
start1:
type: integer
start2:
type: integer
type: object
models.USBCamera:
properties:
device:
type: string
type: object
info:
contact:
email: support@kerberos.io
@@ -11,7 +211,49 @@ info:
termsOfService: https://kerberos.io
title: Swagger Kerberos Agent API
version: "1.0"
paths: {}
paths:
/api/hub/verify:
post:
description: Will verify the hub connectivity.
operationId: verify-hub
parameters:
- description: Config
in: body
name: config
required: true
schema:
$ref: '#/definitions/models.Config'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.APIResponse'
security:
- Bearer: []
summary: Will verify the hub connectivity.
tags:
- config
/api/persistence/verify:
post:
description: Will verify the persistence.
operationId: verify-persistence
parameters:
- description: Config
in: body
name: config
required: true
schema:
$ref: '#/definitions/models.Config'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.APIResponse'
security:
- Bearer: []
summary: Will verify the persistence.
tags:
- config
securityDefinitions:
Bearer:
in: header

View File

@@ -0,0 +1,11 @@
package models
type Media struct {
Key string `json:"key"`
Path string `json:"path"`
Day string `json:"day"`
Time string `json:"time"`
Timestamp string `json:"timestamp"`
CameraName string `json:"camera_name"`
CameraKey string `json:"camera_key"`
}

View File

@@ -71,17 +71,66 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configuratio
cloudTimestamp = communication.CloudTimestamp.Load().(int64)
}
// The total number of recordings stored in the directory
numberOfRecordings := utils.NumberOfFilesInDirectory("./data/recordings")
// The total number of recordings stored in the directory.
recordingDirectory := "./data/recordings"
numberOfRecordings := utils.NumberOfFilesInDirectory(recordingDirectory)
// All days stored in this agent.
days := []string{}
latestEvents := []models.Media{}
files, err := utils.ReadDirectory(recordingDirectory)
if err == nil {
events := utils.GetSortedDirectory(files)
// Get All days
days = utils.GetDays(events, recordingDirectory, configuration)
// Get all latest events
latestEvents = utils.GetMediaFormatted(events, recordingDirectory, configuration, 5) // will get 5 latest recordings.
}
c.JSON(200, gin.H{
"offlineMode": configuration.Config.Offline,
"cameraOnline": lastPacketReceived,
"cloudOnline": cloudTimestamp,
"numberOfRecordings": numberOfRecordings,
"days": days,
"latestEvents": latestEvents,
})
})
api.GET("/latest-events", func(c *gin.Context) {
recordingDirectory := "./data/recordings"
files, err := utils.ReadDirectory(recordingDirectory)
if err == nil {
events := utils.GetSortedDirectory(files)
fileObjects := utils.GetMediaFormatted(events, recordingDirectory, configuration, 0) // will get all recordings from the directory.
c.JSON(200, gin.H{
"events": fileObjects,
})
} else {
c.JSON(400, gin.H{
"data": "Something went wrong: " + err.Error(),
})
}
})
api.GET("/days", func(c *gin.Context) {
recordingDirectory := "./data/recordings"
files, err := utils.ReadDirectory(recordingDirectory)
if err == nil {
events := utils.GetSortedDirectory(files)
days := utils.GetDays(events, recordingDirectory, configuration)
c.JSON(200, gin.H{
"events": days,
})
} else {
c.JSON(400, gin.H{
"data": "Something went wrong: " + err.Error(),
})
}
})
// Streaming handler
api.GET("/stream", func(c *gin.Context) {
// TODO add a token validation!

View File

@@ -63,6 +63,7 @@ func StartServer(configuration *models.Configuration, communication *models.Comm
r.Use(static.Serve("/media", static.LocalFile("./www", true)))
r.Use(static.Serve("/settings", static.LocalFile("./www", true)))
r.Use(static.Serve("/login", static.LocalFile("./www", true)))
r.StaticFS("/file", gin.Dir("./data/recordings", false))
// Run the api on port
err = r.Run(":" + configuration.Port)

View File

@@ -7,9 +7,13 @@ import (
"math/rand"
"os"
"os/exec"
"sort"
"strconv"
"strings"
"time"
"github.com/kerberos-io/agent/machinery/src/log"
"github.com/kerberos-io/agent/machinery/src/models"
)
const letterBytes = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
@@ -55,6 +59,80 @@ func ReadDirectory(directory string) ([]os.FileInfo, error) {
return ff, err
}
func GetSortedDirectory(files []os.FileInfo) []os.FileInfo {
sort.Slice(files, func(i, j int) bool {
return files[i].ModTime().After(files[j].ModTime())
})
return files
}
func GetMediaFormatted(files []os.FileInfo, recordingDirectory string, configuration *models.Configuration, numberOfMedia int) []models.Media {
filePaths := []models.Media{}
count := 0
for _, file := range files {
fileName := file.Name()
fileParts := strings.Split(fileName, "_")
if len(fileParts) == 6 {
timestamp := fileParts[0]
timestampInt, err := strconv.ParseInt(timestamp, 10, 64)
if err == nil {
loc, _ := time.LoadLocation(configuration.Config.Timezone)
time := time.Unix(timestampInt, 0).In(loc)
day := time.Format("02-01-2006")
timeString := time.Format("15:04:05")
media := models.Media{
Key: fileName,
Path: recordingDirectory + "/" + fileName,
CameraName: configuration.Config.Name,
CameraKey: configuration.Config.Key,
Day: day,
Time: timeString,
Timestamp: timestamp,
}
filePaths = append(filePaths, media)
count = count + 1
if numberOfMedia > 0 && count > numberOfMedia {
break
}
}
}
}
return filePaths
}
func GetDays(files []os.FileInfo, recordingDirectory string, configuration *models.Configuration) []string {
days := []string{}
for _, file := range files {
fileName := file.Name()
fileParts := strings.Split(fileName, "_")
if len(fileParts) == 6 {
timestamp := fileParts[0]
timestampInt, err := strconv.ParseInt(timestamp, 10, 64)
if err == nil {
loc, _ := time.LoadLocation(configuration.Config.Timezone)
time := time.Unix(timestampInt, 0).In(loc)
day := time.Format("02-01-2006")
days = append(days, day)
}
}
}
uniqueDays := Unique(days)
return uniqueDays
}
func Unique(intSlice []string) []string {
keys := make(map[string]bool)
list := []string{}
for _, entry := range intSlice {
if _, value := keys[entry]; !value {
keys[entry] = true
list = append(list, entry)
}
}
return list
}
func NumberOfFilesInDirectory(path string) int {
files, _ := ioutil.ReadDir(path)
return len(files)

View File

@@ -20,6 +20,9 @@
"class-methods-use-this": "off",
"react/forbid-prop-types": "off",
"no-case-declarations": "off",
"jsx-a11y/media-has-caption": "off",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"jsx-a11y/label-has-associated-control": [
"error",
{

View File

@@ -42,12 +42,7 @@ class App extends React.Component {
const { children, username, dashboard, dispatchLogout } = this.props;
return (
<div id="page-root">
<Sidebar
logo={logo}
title="Kerberos Agent"
version={config.VERSION}
mobile
>
<Sidebar logo={logo} title="Kerberos Agent" version="beta 1.0" mobile>
<Profilebar
username={username}
email="support@kerberos.io"
@@ -75,7 +70,7 @@ class App extends React.Component {
title="Swagger API docs"
icon="api"
external
link={`${config.API_URL}swagger/index.html`}
link={`${config.URL}/swagger/index.html`}
/>
<NavigationItem
title="Documentation"
@@ -98,9 +93,11 @@ class App extends React.Component {
{dashboard.offlineMode === 'true' && (
<Link to="/settings">
<div className="warning">
<div>
<Icon label="info" />
Attention! Kerberos is currently running in Offline mode.
</div>
</div>
</Link>
)}

View File

@@ -5,20 +5,15 @@
background: var(--upper-gradient);
width: 100%;
color: white;
padding: size(1.5) size(4);
margin: -6px 0 0 0;
display: flex;
align-items: center;
> div {
padding: size(1.5) size(4);
}
svg {
padding-right: size(1);
}
}
a {
display: flex;
color: var(--text);
&:hover, &:active {
color: var(--text);
}
}

View File

@@ -12,8 +12,13 @@ import {
TableRow,
Icon,
Ellipse,
Button,
Card,
SetupBox,
Modal,
ModalHeader,
ModalBody,
ModalFooter,
} from '@kerberos-io/ui';
import './Dashboard.scss';
import ReactTooltip from 'react-tooltip';
@@ -25,6 +30,8 @@ class Dashboard extends React.Component {
super();
this.state = {
liveviewLoaded: false,
open: false,
currentRecording: '',
};
}
@@ -46,13 +53,27 @@ class Dashboard extends React.Component {
}
}
handleClose() {
this.setState({
open: false,
currentRecording: '',
});
}
getCurrentTimestamp() {
return Math.round(Date.now() / 1000);
}
openModal(file) {
this.setState({
open: true,
currentRecording: file,
});
}
render() {
const { dashboard } = this.props;
const { liveviewLoaded } = this.state;
const { liveviewLoaded, open, currentRecording } = this.state;
// We check if the camera was getting a valid frame
// during the last 5 seconds, otherwise we assume the camera is offline.
@@ -70,23 +91,30 @@ class Dashboard extends React.Component {
}
return (
<div>
<div id="dashboard">
<Breadcrumb
title="Dashboard"
level1="Overview of your video surveilance"
level1Link=""
>
{/* <Link to="/deployments">
<Link to="/media">
<Button label="Watch recordings" icon="media" type="default" />
</Link>
<Link to="/settings">
<Button
label="Add Kerberos Agent"
icon="plus-circle"
type="default"
label="Configure"
icon="preferences"
type={isCameraOnline ? 'neutral' : 'default'}
/>
</Link> */}
</Link>
</Breadcrumb>
<div className="stats grid-container --four-columns">
<KPI number="69" divider="0" footer="Number of days" />
<KPI
number={dashboard.days ? dashboard.days.length : 0}
divider="0"
footer="Number of days"
/>
<KPI
number={
dashboard.numberOfRecordings ? dashboard.numberOfRecordings : 0
@@ -128,87 +156,76 @@ class Dashboard extends React.Component {
/>
</TableHeader>
<TableBody>
{dashboard.latestEvents &&
dashboard.latestEvents.map((event) => (
<TableRow
key={event.timestamp}
id="cells1"
bodycells={[
<>
<div className="time">
<Ellipse status="success" />{' '}
<p data-tip="10m and 5s ago">19:45:10</p>
<p data-tip="10m and 5s ago">{event.time}</p>
</div>
</>,
<>
<p>Motion was detected</p>
<p
className="pointer"
onClick={() =>
this.openModal(`${config.URL}/file/${event.key}`)
}
>
Motion was detected
</p>
</>,
<>
<span className="version">Frontdoor</span>&nbsp;
<Icon label="cameras" />
</>,
]}
/>
<TableRow
id="cells1"
bodycells={[
<>
<Ellipse status="success" />{' '}
<p data-tip="10m and 5s ago">18:23:44</p>
</>,
<>
<p>Motion was detected</p>
</>,
<>
<span>Frontdoor</span>&nbsp;
<Icon label="cameras" />
</>,
]}
/>
<TableRow
id="cells1"
bodycells={[
<>
<Ellipse status="success" />{' '}
<p data-tip="10m and 5s ago">18:20:29</p>
</>,
<>
<p>Motion was detected</p>
</>,
<>
<span className="version">Frontdoor</span>&nbsp;
<Icon label="cameras" />
</>,
]}
/>
<TableRow
id="cells1"
bodycells={[
<>
<Ellipse status="success" />{' '}
<p data-tip="10m and 5s ago">15:16:58</p>
</>,
<>
<p>Motion was detected</p>
</>,
<>
<span className="version">Frontdoor</span>&nbsp;
<Icon label="cameras" />
</>,
]}
/>
<TableRow
id="cells1"
bodycells={[
<>
<Ellipse status="success" />{' '}
<p data-tip="10m and 5s ago">10:05:44</p>
</>,
<>
<p>Motion was detected</p>
</>,
<>
<span className="version">Frontdoor</span>&nbsp;
<span className="version">{event.camera_name}</span>
&nbsp;
<Icon label="cameras" />
</>,
]}
/>
))}
</TableBody>
{open && (
<Modal>
<ModalHeader
title="View recording"
onClose={() => this.handleClose()}
/>
<ModalBody>
<video controls autoPlay>
<source src={currentRecording} type="video/mp4" />
</video>
</ModalBody>
<ModalFooter
right={
<>
<a
href={currentRecording}
download="video"
target="_blank"
rel="noreferrer"
>
<Button
label="Download"
icon="download"
type="button"
buttonType="button"
/>
</a>
<Button
label="Close"
icon="cross-circle"
type="button"
buttonType="button"
onClick={() => this.handleClose()}
/>
</>
}
/>
</Modal>
)}
</Table>
</div>
<div>

View File

@@ -1,7 +1,31 @@
hr {
margin-top: 50px;
}
#dashboard {
.dashed {
hr {
margin-top: 50px;
}
.dashed {
padding: 20%;
}
.pointer {
cursor: pointer;
}
a {
display: flex;
color: var(--text);
&:hover, &:active {
color: var(--text);
}
}
.time {
display: flex;
align-items: center;
.ellipse-container {
margin-right: 8px;
}
}
}

View File

@@ -4,8 +4,10 @@ import {
VideoContainer,
VideoCard,
ControlBar,
Button,
Input,
} from '@kerberos-io/ui';
import { Link } from 'react-router-dom';
import styles from './Media.scss';
// eslint-disable-next-line react/prefer-stateless-function
@@ -18,13 +20,9 @@ class Media extends React.Component {
level1="All your recordings in a single place"
level1Link=""
>
{/* <Link to="/deployments">
<Button
label="Add Kerberos Agent"
icon="plus-circle"
type="default"
/>
</Link> */}
<Link to="/settings">
<Button label="Configure" icon="preferences" type="default" />
</Link>
</Breadcrumb>
<ControlBar>

View File

@@ -16,7 +16,7 @@ import {
Icon,
Toggle,
} from '@kerberos-io/ui';
import { withRouter } from 'react-router-dom';
import { Link, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import ImageCanvas from '../../components/ImageCanvas/ImageCanvas';
import './Settings.scss';
@@ -467,11 +467,11 @@ class Settings extends React.Component {
return config ? (
<div id="settings">
<Breadcrumb
title="Settings"
level1="Onboard your camera"
level1Link=""
/>
<Breadcrumb title="Settings" level1="Onboard your camera" level1Link="">
<Link to="/media">
<Button label="Watch recordings" icon="media" type="default" />
</Link>
</Breadcrumb>
<ControlBar type="row">
<Tabs>
<Tab