Compare commits

...

46 Commits

Author SHA1 Message Date
f709807ce7 ignore 2024-09-04 16:48:46 +08:00
207001c1f8 rm idea 2024-09-04 16:48:09 +08:00
43ffa61470 workflow_dispatch 2024-09-04 14:00:32 +08:00
0b7b5bc54e mv
All checks were successful
Template Cleanup / Template Cleanup (push) Has been skipped
2024-09-04 13:57:22 +08:00
78ae4ca00b chore
All checks were successful
Template Cleanup / Template Cleanup (push) Has been skipped
2024-09-04 13:07:00 +08:00
e67a294ff6 chore
All checks were successful
Template Cleanup / Template Cleanup (push) Has been skipped
2024-09-04 12:53:08 +08:00
ee5efb0f5a git push
All checks were successful
Template Cleanup / Template Cleanup (push) Has been skipped
2024-09-04 12:40:52 +08:00
87da521e2e fix
All checks were successful
Template Cleanup / Template Cleanup (push) Has been skipped
2024-09-04 12:33:50 +08:00
d39363c8a1 fix 2024-09-04 12:28:42 +08:00
b90db27e15 rename
All checks were successful
Template Cleanup / Template Cleanup (push) Has been skipped
2024-09-04 11:44:42 +08:00
8b7e7a3f85 convert to template
All checks were successful
Template Cleanup / Template Cleanup (push) Has been skipped
2024-09-04 11:36:03 +08:00
3085195290 fix
All checks were successful
Template Cleanup / Template Cleanup (push) Successful in 3s
2024-09-04 11:10:59 +08:00
ce77821c2b fix
All checks were successful
Template Cleanup / Template Cleanup (push) Successful in 3s
2024-09-04 11:08:52 +08:00
6c98a35488 fix
All checks were successful
Template Cleanup / Template Cleanup (push) Successful in 3s
2024-09-04 11:07:40 +08:00
99c9303517 fix
All checks were successful
Template Cleanup / Template Cleanup (push) Successful in 3s
2024-09-04 11:06:24 +08:00
4262b1aaea fix
All checks were successful
Template Cleanup / Template Cleanup (push) Has been skipped
2024-09-04 11:04:03 +08:00
e28fd1a0b9 chore
All checks were successful
Template Cleanup / Template Cleanup (push) Has been skipped
2024-09-04 11:02:32 +08:00
f4c8b5ed21 fix
All checks were successful
Template Cleanup / Template Cleanup (push) Has been skipped
2024-09-04 10:51:10 +08:00
f2c5cb5f8d rename github to gitea
All checks were successful
Template Cleanup / Template Cleanup (push) Has been skipped
2024-09-04 10:42:12 +08:00
c2683ab03f ci: add template cleanup CI file
All checks were successful
Template Cleanup / Template Cleanup (push) Has been skipped
2024-09-04 10:38:30 +08:00
6539495e4d docs: add README.md 2024-09-04 10:05:07 +08:00
8871ebeb41 rm: 删除 zbx agent plugin cli 入口 2024-08-27 16:12:37 +08:00
4fbdbb15cc chore: 删除无用注释 2024-08-27 15:30:34 +08:00
b1ad068073 chore: 添加注册设备条目数 2024-08-27 15:05:25 +08:00
254b41e160 feat: 改造 zbx 路由为 RPC 风格,添加了通知消费者接口 2024-08-27 14:42:11 +08:00
c8b33a27f6 feat: 为通知接口添加CRUD 2024-08-27 14:23:19 +08:00
ae9fd0d5b0 chore: 添加关闭文件处理 2024-08-27 12:57:06 +08:00
2bd63388cc chore: compose 改名文件 2024-08-27 11:38:06 +08:00
a641d6846d fix: zbx 模式下 sessions 的管理 2024-08-27 09:33:23 +08:00
67de79e230 feat: 为 zbx 添加批量注册设备能力 2024-08-26 16:31:53 +08:00
0ef8af021f feat(onvif): 添加通知存储 2024-08-26 15:21:47 +08:00
ff5cc66a55 chore 2024-08-26 15:01:14 +08:00
63fb12901b refactor 2024-08-26 14:52:31 +08:00
219116c961 refactor: 迁移 service 代码逻辑 2024-08-26 14:50:46 +08:00
de3667f957 chore 2024-08-26 14:43:22 +08:00
82d3f99404 refactor: 重命名 connection -> session 2024-08-26 14:41:57 +08:00
a6cc98a32b refactor: 重命名 callback 2024-08-26 14:31:36 +08:00
2a1852188e chore: 修改 app version 显示样式 2024-08-26 14:12:23 +08:00
7eee4cfc26 refactor: 重构 zabbix 功能到集成模块中 2024-08-26 14:09:45 +08:00
00d2b30658 feat(zabbix): 添加 zabbix 自动发现接口 2024-08-26 12:27:47 +08:00
0493c3b1e6 chore: edit readme 2024-08-26 11:21:23 +08:00
74c955a3f7 feat(response): 统一结构返回 2024-08-26 11:19:18 +08:00
85c90ec1e1 chore: add comment 2024-08-26 09:57:44 +08:00
efae826049 chore 2024-08-26 09:31:28 +08:00
b5c1ec3bda chore 2024-08-26 09:26:13 +08:00
8992b65e76 chore: 删除测试代码 2024-08-23 17:05:35 +08:00
29 changed files with 422 additions and 380 deletions

View File

@ -0,0 +1,51 @@
name: Template Cleanup
on:
# create:
# push:
# branches: [ main ]
workflow_dispatch:
jobs:
# 当前仓库名不为 zabbix-agent2-plugin-template 时触发
template-cleanup:
name: Template Cleanup
runs-on: ubuntu-latest
if: github.event.repository.name != 'zabbix-agent2-plugin-template'
permissions:
contents: write
steps:
- name: Fetch Sources
uses: actions/checkout@v4
# 项目清理
- name: Cleanup
run: |
export LC_CTYPE=C
export LANG=C
# 准备变量
OWNER=$(echo $GITHUB_REPOSITORY_OWNER | tr '[:upper:]' '[:lower:]')
REPO_NAME="${GITHUB_REPOSITORY##*/}"
SAFE_OWNER=$(echo $OWNER | sed 's/[^a-zA-Z0-9]//g' | tr '[:upper:]' '[:lower:]')
SAFE_REPO_NAME=$(echo $REPO_NAME | sed 's/[^a-zA-Z0-9]//g' | tr '[:upper:]' '[:lower:]')
PLUGIN_NAME=$(echo "$SAFE_REPO_NAME" | sed 's/^\(.\)/\U\1/')
# 替换包名
find . -type f -exec sed -i "s/zabbixagent2plugintemplate/$SAFE_REPO_NAME/g" {} +
find . -type f -exec sed -i "s/Zabbixagent2plugintemplate/$PLUGIN_NAME/g" {} +
find . -type f -exec sed -i "s/Zabbix Agent 2 Plugin Template/$REPO_NAME/g" {} +
- name: Commit files
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add .
git commit -m "Template cleanup"
- name: Push changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref }}

138
.gitignore vendored
View File

@ -13,29 +13,6 @@
# .nfs files are created when an open file is removed but is still being accessed # .nfs files are created when an open file is removed but is still being accessed
.nfs* .nfs*
### Go template
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
### Windows template ### Windows template
# Windows thumbnail cache files # Windows thumbnail cache files
Thumbs.db Thumbs.db
@ -62,85 +39,6 @@ $RECYCLE.BIN/
# Windows shortcuts # Windows shortcuts
*.lnk *.lnk
### GoLand template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### macOS template ### macOS template
# General # General
.DS_Store .DS_Store
@ -171,6 +69,38 @@ Temporary Items
# Application # Application
log/ log/
config.dev.yaml
compose.dev.yaml ### Go template
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
### GoLand
# General
.idea/
# CMake
cmake-build-*/
# IntelliJ
out/

8
.idea/.gitignore generated vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxBlameSettings">
<option name="version" value="2" />
</component>
</project>

8
.idea/modules.xml generated
View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/onvif-agent.iml" filepath="$PROJECT_DIR$/.idea/onvif-agent.iml" />
</modules>
</component>
</project>

13
.idea/onvif-agent.iml generated
View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true">
<buildTags>
<option name="os" value="linux" />
</buildTags>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -5,5 +5,5 @@ RUN GOPROXY=https://goproxy.cn go mod download
RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o out/app RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o out/app
FROM zabbix/zabbix-agent2:ubuntu-7.0-latest FROM zabbix/zabbix-agent2:ubuntu-7.0-latest
COPY --from=builder /build/out/app /usr/sbin/zabbix-agent2-plugin/onvif-agent COPY --from=builder /build/out/app /usr/sbin/zabbix-agent2-plugin/zabbixagent2plugintemplate
RUN echo "Plugins.ONVIF.System.Path=/usr/sbin/zabbix-agent2-plugin/onvif-agent" >> /etc/zabbix/zabbix_agent2.d/plugins.d/onvif.conf RUN echo "Plugins.Zabbixagent2plugintemplate.System.Path=/usr/sbin/zabbix-agent2-plugin/zabbixagent2plugintemplate" >> /etc/zabbix/zabbix_agent2.d/plugins.d/zabbixagent2plugintemplate.conf

View File

@ -1,15 +1,14 @@
# Wukong: Simplified Management of ONVIF IP Devices # Zabbix Agent 2 Plugin Template
Wukong is a robust implementation of the ONVIF protocol designed for the efficient management of ONVIF-compliant IP devices, including cameras. This project aims to provide a user-friendly and streamlined solution for the convenient management of IP cameras and other devices that adhere to the ONVIF standard. ## 快速开始
With Wukong, users can easily configure, monitor, and control their ONVIF devices, enhancing the overall experience of managing surveillance systems and ensuring seamless integration within various environments. ### 先决条件
## Requirements - Go >= 1.20
- Zabbix agent 2 >=6.0.0
- [预编译二进制文件](https://www.zabbix.com/download_agents)
- [Docker 镜像](https://hub.docker.com/r/zabbix/zabbix-agent2)
- Zabbix agent 2 version 6.0.0 or newer ### 从源代码构建
- Go programming language version 1.20 or newer (required only for building the plugin from the source)
## Installation
The plugin can be compiled using `go build`.
`go build`.

View File

@ -5,14 +5,14 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"io" "io"
"log" "log"
"onvif-agent/config"
"onvif-agent/constant"
"onvif-agent/integration/zabbixagent"
"onvif-agent/router"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"time" "time"
"zabbixagent2plugintemplate/config"
"zabbixagent2plugintemplate/constant"
"zabbixagent2plugintemplate/integration/zabbixagent"
"zabbixagent2plugintemplate/router"
) )
func main() { func main() {
@ -32,7 +32,12 @@ func main() {
log.Fatalf("Error opening file: %v", err) log.Fatalf("Error opening file: %v", err)
} }
defer f.Close() defer func(f *os.File) {
err := f.Close()
if err != nil {
log.Fatalf("Error closing file: %v", err)
}
}(f)
writer := io.MultiWriter(os.Stdout, f) writer := io.MultiWriter(os.Stdout, f)
log.SetOutput(writer) log.SetOutput(writer)
@ -60,7 +65,7 @@ func main() {
}() }()
/** /**
* Zabbix agent * Zabbix agent integration
*/ */
go func() { go func() {
err = zabbixagent.Launch() err = zabbixagent.Launch()

View File

@ -1,15 +1,13 @@
services: services:
onvif-agent: zabbixagent2plugintemplate:
build: build: .
context: .
dockerfile: zabbixagent.dockerfile
environment: environment:
ZBX_HOSTNAME: ONVIF ZBX_HOSTNAME: ONVIF
ZBX_SERVER_HOST: server ZBX_SERVER_HOST: server
ZBX_SERVER_PORT: 10051 ZBX_SERVER_PORT: 10051
volumes: volumes:
- ./log:/var/log/onvif-agent - ./log:/var/log/zabbixagent2plugintemplate
- ./config.yaml:/etc/onvif-agent/config.yaml - ./config.yaml:/etc/zabbixagent2plugintemplate/config.yaml
ports: ports:
- "8080:8080" - "8080:8080"
networks: networks:

View File

@ -1,4 +1,4 @@
app: app:
port: 8080 port: 8080
host: "0.0.0.0" host: "0.0.0.0"
url: "http://onvif-agent:8080" url: "http://zabbixagent2plugintemplate:8080"

View File

@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"github.com/spf13/viper" "github.com/spf13/viper"
"log" "log"
"onvif-agent/constant"
"os" "os"
"zabbixagent2plugintemplate/constant"
) )
type config struct { type config struct {

View File

@ -1,6 +1,6 @@
package constant package constant
const ( const (
AppName = "onvif-agent" AppName = "zabbixagent2plugintemplate"
AppVersion = "1.0.0" AppVersion = "1.0.0"
) )

2
go.mod
View File

@ -1,4 +1,4 @@
module onvif-agent module zabbixagent2plugintemplate
go 1.23.0 go 1.23.0

View File

@ -1,5 +1,5 @@
package zabbixagent package zabbixagent
const ( const (
PluginName = "ONVIF" PluginName = "Zabbixagent2plugintemplate"
) )

View File

@ -0,0 +1,72 @@
package zabbixagent
import (
"fmt"
"github.com/gin-gonic/gin"
"zabbixagent2plugintemplate/response"
handler "zabbixagent2plugintemplate/router/handler/onvif"
service "zabbixagent2plugintemplate/service/onvif"
)
func DiscoverONVIFDevices(c *gin.Context) {
type device struct {
Xaddr string `json:"{#XADDR}"`
}
devices := make([]device, 0)
for xaddr := range service.Sessions {
devices = append(devices, device{
Xaddr: xaddr,
})
}
response.NewResponse().WithData(devices).Send(c)
}
func RegisterONVIFDevices(c *gin.Context) {
var req []handler.CreateSessionRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.NewResponse().Error(err).Send(c)
return
}
// 用于替换已存在的 Sessions
var sessions = make(map[string]*service.Session)
for _, v := range req {
session, err := service.NewSession(v.Xaddr, v.Username, v.Password)
if err != nil {
response.NewResponse().Error(err).Send(c)
return
}
_, err = session.GetDeviceInfo()
if err != nil {
response.NewResponse().Error(err).Send(c)
return
}
sessions[session.Device.GetDeviceParams().Xaddr] = session
}
service.Sessions = sessions
response.NewResponse().Success().WithMessage(fmt.Sprintf("%d device(s) registered", len(sessions))).Send(c)
}
type ConsumeNotificationsRequest struct {
Xaddr string `json:"xaddr"`
}
func ConsumeONVIFNotifications(c *gin.Context) {
var req ConsumeNotificationsRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.NewResponse().Error(err).Send(c)
return
}
n := service.Notifications[req.Xaddr]
delete(service.Notifications, req.Xaddr)
response.NewResponse().WithData(n).Send(c)
}

View File

@ -6,9 +6,9 @@ import (
"golang.zabbix.com/sdk/errs" "golang.zabbix.com/sdk/errs"
"io" "io"
"net/http" "net/http"
"onvif-agent/config"
"onvif-agent/constant"
"strings" "strings"
"zabbixagent2plugintemplate/config"
"zabbixagent2plugintemplate/constant"
) )
// HandlerFunc describes the signature all metric handler functions must have. // HandlerFunc describes the signature all metric handler functions must have.
@ -24,7 +24,7 @@ type Handler struct {
} }
func (h *Handler) GetAppVersion(_ context.Context, _ map[string]string, _ ...string) (any, error) { func (h *Handler) GetAppVersion(_ context.Context, _ map[string]string, _ ...string) (any, error) {
return constant.AppVersion, nil return fmt.Sprintf("%s %s", constant.AppName, constant.AppVersion), nil
} }
func (h *Handler) HTTPClient(ctx context.Context, params map[string]string, _ ...string) (any, error) { func (h *Handler) HTTPClient(ctx context.Context, params map[string]string, _ ...string) (any, error) {
@ -35,7 +35,7 @@ func (h *Handler) HTTPClient(ctx context.Context, params map[string]string, _ ..
url := params["url"] url := params["url"]
if !strings.HasPrefix(url, "http") { if !strings.HasPrefix(url, "http") {
url = fmt.Sprintf("http://127.0.0.1:%d%s", config.Config.App.Port, url) url = fmt.Sprintf("http://localhost:%d%s", config.Config.App.Port, url)
} }
body := params["body"] body := params["body"]
@ -57,7 +57,7 @@ func (h *Handler) HTTPClient(ctx context.Context, params map[string]string, _ ..
return nil, errs.Wrapf(err, "failed to read the response") return nil, errs.Wrapf(err, "failed to read the response")
} }
return method + url + body + string(data), nil return string(data), nil
} }
// NewHandler creates a new handler with initialized clients for system and tcp calls. // NewHandler creates a new handler with initialized clients for system and tcp calls.

View File

@ -1,61 +0,0 @@
package zabbixagent
import (
"errors"
"fmt"
"os"
"golang.zabbix.com/sdk/plugin/flag"
"golang.zabbix.com/sdk/zbxerr"
)
const copyrightMessage = //
`Copyright 2001-%d imbytecat
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
`
const (
pluginVersionMajor = 1
pluginVersionMinor = 0
pluginVersionPatch = 0
pluginVersionRC = "alpha1"
pluginLicenseYear = 2024
)
func Run() {
err := flag.HandleFlags(
PluginName,
os.Args[0],
fmt.Sprintf(copyrightMessage, pluginLicenseYear),
pluginVersionRC,
pluginVersionMajor,
pluginVersionMinor,
pluginVersionPatch,
)
if err != nil {
if errors.Is(err, zbxerr.ErrorOSExitZero) {
return
}
panic(err)
}
err = Launch()
if err != nil {
return
}
panic(err)
}

73
response/response.go Normal file
View File

@ -0,0 +1,73 @@
package response
import (
"encoding/json"
"github.com/gin-gonic/gin"
"net/http"
)
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
}
func NewResponse() *Response {
return &Response{
Code: http.StatusOK,
Message: "success",
}
}
func (r *Response) WithCode(code int) *Response {
r.Code = code
return r
}
func (r *Response) WithMessage(message string) *Response {
r.Message = message
return r
}
func (r *Response) WithData(data any) *Response {
r.Data = data
return r
}
func (r *Response) Error(err error) *Response {
r.Code = http.StatusInternalServerError
r.Message = err.Error()
return r
}
func (r *Response) Success(message ...string) *Response {
r.Code = http.StatusOK
if len(message) > 0 {
r.Message = message[0]
} else {
r.Message = "success"
}
return r
}
func (r *Response) Fail(message ...string) *Response {
r.Code = http.StatusInternalServerError
if len(message) > 0 {
r.Message = message[0]
} else {
r.Message = "fail"
}
return r
}
func (r *Response) Send(c *gin.Context) {
c.JSON(http.StatusOK, r)
}
func (r *Response) ToJSON() (string, error) {
data, err := json.Marshal(r)
if err != nil {
return "", err
}
return string(data), nil
}

View File

@ -2,10 +2,9 @@ package handler
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"zabbixagent2plugintemplate/response"
) )
func Hello(c *gin.Context) { func Hello(c *gin.Context) {
c.JSON(200, gin.H{ response.NewResponse().Success("Hello, World!").Send(c)
"message": "Hello, World!",
})
} }

View File

@ -1,99 +0,0 @@
package onvif
import (
"github.com/gin-gonic/gin"
"net/http"
"onvif-agent/service/onvif"
)
var conns = make(map[string]*onvif.Connection)
type CreateConnectionRequest struct {
Xaddr string `json:"xaddr"`
Username string `json:"username"`
Password string `json:"password"`
}
func CreateConnection(c *gin.Context) {
var req CreateConnectionRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
conn, err := onvif.New(req.Xaddr, req.Username, req.Password)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
info, err := conn.GetDeviceInfo()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
// store connection
conns[conn.Device.GetDeviceParams().Xaddr] = conn
c.JSON(http.StatusOK, gin.H{
"device": info,
})
}
func GetConnections(c *gin.Context) {
devices := make(map[string]interface{})
for xaddr, conn := range conns {
info, err := conn.GetDeviceInfo()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
devices[xaddr] = info
}
c.JSON(http.StatusOK, gin.H{
"connections": devices,
})
}
func GetConnectionByXaddr(c *gin.Context) {
xaddr := c.Param("xaddr")
conn := conns[xaddr]
if conn == nil {
c.JSON(http.StatusNotFound, gin.H{
"message": "Connection not found",
})
return
}
info, err := conn.GetDeviceInfo()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"device": info,
})
}
func DeleteConnection(c *gin.Context) {
xaddr := c.Param("xaddr")
delete(conns, xaddr)
c.JSON(http.StatusOK, gin.H{
"message": "OK",
})
}

View File

@ -0,0 +1,23 @@
package onvif
import (
"github.com/gin-gonic/gin"
"zabbixagent2plugintemplate/response"
"zabbixagent2plugintemplate/service/onvif"
)
func GetNotifications(c *gin.Context) {
response.NewResponse().WithData(onvif.Notifications).Send(c)
}
func GetNotificationByXaddr(c *gin.Context) {
xaddr := c.Param("xaddr")
response.NewResponse().WithData(onvif.Notifications[xaddr]).Send(c)
}
func DeleteNotificationByXaddr(c *gin.Context) {
xaddr := c.Param("xaddr")
delete(onvif.Notifications, xaddr)
response.NewResponse().Success().Send(c)
}

View File

@ -0,0 +1,79 @@
package onvif
import (
"github.com/gin-gonic/gin"
"net/http"
"zabbixagent2plugintemplate/response"
"zabbixagent2plugintemplate/service/onvif"
)
type CreateSessionRequest struct {
Xaddr string `json:"xaddr"`
Username string `json:"username"`
Password string `json:"password"`
}
func CreateSession(c *gin.Context) {
var req CreateSessionRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.NewResponse().Error(err).Send(c)
return
}
session, err := onvif.NewSession(req.Xaddr, req.Username, req.Password)
if err != nil {
response.NewResponse().Error(err).Send(c)
return
}
info, err := session.GetDeviceInfo()
if err != nil {
response.NewResponse().Error(err).Send(c)
return
}
onvif.Sessions[session.Device.GetDeviceParams().Xaddr] = session
response.NewResponse().WithData(info).Send(c)
}
func GetSessions(c *gin.Context) {
devices := make(map[string]any)
for xaddr, session := range onvif.Sessions {
info, err := session.GetDeviceInfo()
if err != nil {
response.NewResponse().Error(err).Send(c)
return
}
devices[xaddr] = info
}
response.NewResponse().WithData(devices).Send(c)
}
func GetSessionByXaddr(c *gin.Context) {
xaddr := c.Param("xaddr")
session := onvif.Sessions[xaddr]
if session == nil {
response.NewResponse().Fail("Session not found").WithCode(http.StatusNotFound).Send(c)
return
}
info, err := session.GetDeviceInfo()
if err != nil {
response.NewResponse().Error(err).Send(c)
return
}
response.NewResponse().WithData(info).Send(c)
}
func DeleteSessionByXaddr(c *gin.Context) {
xaddr := c.Param("xaddr")
delete(onvif.Sessions, xaddr)
response.NewResponse().Success().Send(c)
}

View File

@ -7,48 +7,43 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"log" "log"
"net/http" "net/http"
"onvif-agent/config" "zabbixagent2plugintemplate/config"
"zabbixagent2plugintemplate/response"
"zabbixagent2plugintemplate/service/onvif"
) )
func CreateEventSubscription(c *gin.Context) { func CreateSubscription(c *gin.Context) {
xaddr := c.Param("xaddr") xaddr := c.Param("xaddr")
callbackURL := event.AttributedURIType(fmt.Sprintf("%s/onvif/subscriptions/%s/callback", config.Config.App.URL, xaddr)) callbackURL := event.AttributedURIType(fmt.Sprintf("%s/onvif/subscriptions/%s/callback", config.Config.App.URL, xaddr))
log.Printf("CreateEventSubscription callback URL: %s", callbackURL) log.Printf("CreateSubscription callback URL: %s", callbackURL)
conn := conns[xaddr] session := onvif.Sessions[xaddr]
if conn == nil { if session == nil {
c.JSON(http.StatusNotFound, gin.H{ response.NewResponse().Fail("Connection not found").WithCode(http.StatusNotFound).Send(c)
"message": "Connection not found",
})
return return
} }
result, err := conn.SubscribeEvents(callbackURL, "PT60S") result, err := session.EventSubscribe(callbackURL, "PT60S")
if err != nil { if err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{ response.NewResponse().Error(err).Send(c)
"message": err.Error(),
})
return return
} }
c.JSON(http.StatusOK, result) response.NewResponse().Success().WithData(result).Send(c)
} }
func EventNotifyCallback(c *gin.Context) { func NotifyCallback(c *gin.Context) {
xaddr := c.Param("xaddr") xaddr := c.Param("xaddr")
log.Printf("EventNotifyCallback from: %s", xaddr) log.Printf("NotifyCallback xaddr: %s", xaddr)
var notify event.Notify var notify event.Notify
envelope := gosoap.NewSOAPEnvelope(&notify) envelope := gosoap.NewSOAPEnvelope(&notify)
if err := c.ShouldBindXML(&envelope); err != nil { if err := c.ShouldBindXML(&envelope); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) response.NewResponse().Error(err).WithCode(http.StatusBadRequest).Send(c)
return return
} }
// TODO: handle notifications onvif.Notifications[xaddr] = append(onvif.Notifications[xaddr], notify.NotificationMessage...)
for _, msg := range envelope.Body.Content.(*event.Notify).NotificationMessage {
log.Printf("Topic: %s, Message: %s", msg.Topic.TopicKinds, msg.Message.Message)
}
c.JSON(http.StatusOK, gin.H{"message": "OK"}) response.NewResponse().Success().Send(c)
} }

View File

@ -2,27 +2,42 @@ package router
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"onvif-agent/router/handler" "zabbixagent2plugintemplate/integration/zabbixagent"
"onvif-agent/router/handler/onvif" "zabbixagent2plugintemplate/router/handler"
"zabbixagent2plugintemplate/router/handler/onvif"
) )
func SetupRoutes(r *gin.Engine) { func SetupRoutes(r *gin.Engine) {
r.GET("/", handler.Hello) r.GET("/", handler.Hello)
userGroup := r.Group("/onvif") onvifGroup := r.Group("/onvif")
{ {
connectionGroup := userGroup.Group("/connections") connectionGroup := onvifGroup.Group("/sessions")
{ {
connectionGroup.POST("/", onvif.CreateConnection) connectionGroup.POST("/", onvif.CreateSession)
connectionGroup.GET("/", onvif.GetConnections) connectionGroup.GET("/", onvif.GetSessions)
connectionGroup.GET("/:xaddr", onvif.GetConnectionByXaddr) connectionGroup.GET("/:xaddr", onvif.GetSessionByXaddr)
connectionGroup.DELETE("/:xaddr", onvif.DeleteConnection) connectionGroup.DELETE("/:xaddr", onvif.DeleteSessionByXaddr)
} }
subscriptionGroup := userGroup.Group("/subscriptions") subscriptionGroup := onvifGroup.Group("/subscriptions")
{ {
subscriptionGroup.POST("/:xaddr", onvif.CreateEventSubscription) subscriptionGroup.POST("/:xaddr", onvif.CreateSubscription)
subscriptionGroup.POST("/:xaddr/callback", onvif.EventNotifyCallback) subscriptionGroup.POST("/:xaddr/callback", onvif.NotifyCallback)
}
notificationGroup := onvifGroup.Group("/notifications")
{
notificationGroup.GET("/", onvif.GetNotifications)
notificationGroup.GET("/:xaddr", onvif.GetNotificationByXaddr)
notificationGroup.DELETE("/:xaddr", onvif.DeleteNotificationByXaddr)
} }
} }
zabbixGroup := r.Group("/zabbix")
{
zabbixGroup.POST("/DiscoverONVIFDevices", zabbixagent.DiscoverONVIFDevices)
zabbixGroup.POST("/RegisterONVIFDevices", zabbixagent.RegisterONVIFDevices)
zabbixGroup.POST("/ConsumeONVIFNotifications", zabbixagent.ConsumeONVIFNotifications)
}
} }

View File

@ -2,7 +2,7 @@ package onvif
import "github.com/IOTechSystems/onvif/device" import "github.com/IOTechSystems/onvif/device"
func (c *Connection) GetDeviceInfo() (*device.GetDeviceInformationResponse, error) { func (c *Session) GetDeviceInfo() (*device.GetDeviceInformationResponse, error) {
resp, err := c.Device.CallMethod(device.GetDeviceInformation{}) resp, err := c.Device.CallMethod(device.GetDeviceInformation{})
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -5,7 +5,9 @@ import (
"github.com/IOTechSystems/onvif/xsd" "github.com/IOTechSystems/onvif/xsd"
) )
func (c *Connection) SubscribeEvents( var Notifications = make(map[string][]event.NotificationMessage)
func (c *Session) EventSubscribe(
consumerAddress event.AttributedURIType, consumerAddress event.AttributedURIType,
terminationTime xsd.String, // PT60S terminationTime xsd.String, // PT60S
) (*event.SubscribeResponse, error) { ) (*event.SubscribeResponse, error) {

View File

@ -5,16 +5,18 @@ import (
"strings" "strings"
) )
type Connection struct { type Session struct {
Device *onvif.Device `json:"device"` Device *onvif.Device `json:"device"`
} }
func New(xaddr string, username string, password string) (*Connection, error) { var Sessions = make(map[string]*Session)
func NewSession(xaddr string, username string, password string) (*Session, error) {
// 规范化连接地址 // 规范化连接地址
if !strings.Contains(xaddr, ":") { if !strings.Contains(xaddr, ":") {
xaddr += ":80" xaddr += ":80"
} }
dev, err := onvif.NewDevice(onvif.DeviceParams{ dev, err := onvif.NewDevice(onvif.DeviceParams{
Xaddr: xaddr, Xaddr: xaddr,
Username: username, Username: username,
@ -25,5 +27,5 @@ func New(xaddr string, username string, password string) (*Connection, error) {
return nil, err return nil, err
} }
return &Connection{Device: dev}, nil return &Session{Device: dev}, nil
} }