diff --git a/machinery/docs/docs.go b/machinery/docs/docs.go index cdc2101..0925b35 100644 --- a/machinery/docs/docs.go +++ b/machinery/docs/docs.go @@ -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", diff --git a/machinery/docs/swagger.json b/machinery/docs/swagger.json index 19b514c..c98643c 100644 --- a/machinery/docs/swagger.json +++ b/machinery/docs/swagger.json @@ -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", diff --git a/machinery/docs/swagger.yaml b/machinery/docs/swagger.yaml index fc119f8..a236786 100644 --- a/machinery/docs/swagger.yaml +++ b/machinery/docs/swagger.yaml @@ -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 diff --git a/machinery/src/models/Media.go b/machinery/src/models/Media.go new file mode 100644 index 0000000..c05d833 --- /dev/null +++ b/machinery/src/models/Media.go @@ -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"` +} diff --git a/machinery/src/routers/http/Routes.go b/machinery/src/routers/http/Routes.go index 6ef078f..03f39e4 100644 --- a/machinery/src/routers/http/Routes.go +++ b/machinery/src/routers/http/Routes.go @@ -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! diff --git a/machinery/src/routers/http/Server.go b/machinery/src/routers/http/Server.go index ed9c42e..b72028f 100644 --- a/machinery/src/routers/http/Server.go +++ b/machinery/src/routers/http/Server.go @@ -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) diff --git a/machinery/src/utils/main.go b/machinery/src/utils/main.go index 4aa8dba..289f51e 100644 --- a/machinery/src/utils/main.go +++ b/machinery/src/utils/main.go @@ -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) diff --git a/ui/.eslintrc.json b/ui/.eslintrc.json index 574dc31..f7d3369 100644 --- a/ui/.eslintrc.json +++ b/ui/.eslintrc.json @@ -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", { diff --git a/ui/src/App.jsx b/ui/src/App.jsx index e8a8fe4..e78e555 100644 --- a/ui/src/App.jsx +++ b/ui/src/App.jsx @@ -42,12 +42,7 @@ class App extends React.Component { const { children, username, dashboard, dispatchLogout } = this.props; return (
- +
- - Attention! Kerberos is currently running in Offline mode. +
+ + Attention! Kerberos is currently running in Offline mode. +
)} diff --git a/ui/src/App.scss b/ui/src/App.scss index ad4e5e2..efd97f6 100644 --- a/ui/src/App.scss +++ b/ui/src/App.scss @@ -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); - } } \ No newline at end of file diff --git a/ui/src/pages/Dashboard/Dashboard.jsx b/ui/src/pages/Dashboard/Dashboard.jsx index 0b90da1..f6cb766 100644 --- a/ui/src/pages/Dashboard/Dashboard.jsx +++ b/ui/src/pages/Dashboard/Dashboard.jsx @@ -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 ( -
+
- {/* + +
diff --git a/ui/src/pages/Dashboard/Dashboard.scss b/ui/src/pages/Dashboard/Dashboard.scss index a83794c..b304dcb 100644 --- a/ui/src/pages/Dashboard/Dashboard.scss +++ b/ui/src/pages/Dashboard/Dashboard.scss @@ -1,7 +1,31 @@ -hr { - margin-top: 50px; -} +#dashboard { -.dashed { - padding: 20%; + 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; + } + } } \ No newline at end of file diff --git a/ui/src/pages/Media/Media.jsx b/ui/src/pages/Media/Media.jsx index 3a62873..d029e6e 100644 --- a/ui/src/pages/Media/Media.jsx +++ b/ui/src/pages/Media/Media.jsx @@ -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="" > - {/* -