Compare commits

...

57 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
228ec26904 fix: zabbix 请求 localhost 出现 404 2024-08-23 17:04:39 +08:00
0f4c565250 chore: rename metric 2024-08-23 15:58:03 +08:00
d82a83cca1 chore: remove unused env 2024-08-23 15:21:06 +08:00
09d4d305cd chore: rename plugin path 2024-08-23 15:20:19 +08:00
d4bc25fa59 chore: 简化代码 2024-08-23 15:12:34 +08:00
fcce8f78b0 chore: 修改 zabbix hostname 2024-08-23 15:06:57 +08:00
a417edcfed fix: plugin config name 2024-08-23 14:48:28 +08:00
77d8153b43 chore: 插件改名 2024-08-23 14:47:14 +08:00
28482d201d fix: docker compose 开放 web 端口 2024-08-23 14:22:41 +08:00
6294a2ea3a chore: rename compose service name 2024-08-23 14:13:35 +08:00
1e2a8fb6c7 feat: block main func with zabbix agent plugin 2024-08-23 14:09:32 +08:00
33 changed files with 465 additions and 417 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*
### 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 thumbnail cache files
Thumbs.db
@ -62,85 +39,6 @@ $RECYCLE.BIN/
# Windows shortcuts
*.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
# General
.DS_Store
@ -171,6 +69,38 @@ Temporary Items
# Application
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>

9
Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM golang:1.23 AS builder
WORKDIR /build
COPY . ./
RUN GOPROXY=https://goproxy.cn go mod download
RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o out/app
FROM zabbix/zabbix-agent2:ubuntu-7.0-latest
COPY --from=builder /build/out/app /usr/sbin/zabbix-agent2-plugin/zabbixagent2plugintemplate
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,22 +5,21 @@ import (
"github.com/gin-gonic/gin"
"io"
"log"
"onvif-agent/config"
"onvif-agent/constant"
"onvif-agent/integration/zabbixagent"
"onvif-agent/router"
"os"
"path/filepath"
"runtime"
"time"
"zabbixagent2plugintemplate/config"
"zabbixagent2plugintemplate/constant"
"zabbixagent2plugintemplate/integration/zabbixagent"
"zabbixagent2plugintemplate/router"
)
func main() {
/**
* Logging
*/
date := time.Now().Format("2006-01-02")
logFile := fmt.Sprintf("%s.log", date)
logFile := fmt.Sprintf("%s.log", time.Now().Format("2006-01-02"))
var logFilePath string
if runtime.GOOS == "linux" {
logFilePath = fmt.Sprintf("/var/log/%s/%s", constant.AppName, logFile)
@ -33,7 +32,12 @@ func main() {
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)
log.SetOutput(writer)
@ -61,10 +65,15 @@ func main() {
}()
/**
* Zabbix agent
* Zabbix agent integration
*/
go func() {
zabbixagent.Run()
err = zabbixagent.Launch()
if err != nil {
return
}
panic(err)
}()
select {}

19
compose.yaml Normal file
View File

@ -0,0 +1,19 @@
services:
zabbixagent2plugintemplate:
build: .
environment:
ZBX_HOSTNAME: ONVIF
ZBX_SERVER_HOST: server
ZBX_SERVER_PORT: 10051
volumes:
- ./log:/var/log/zabbixagent2plugintemplate
- ./config.yaml:/etc/zabbixagent2plugintemplate/config.yaml
ports:
- "8080:8080"
networks:
- zabbix-network
restart: unless-stopped
networks:
zabbix-network:
external: true

View File

@ -1,19 +0,0 @@
services:
zabbixagent:
build:
context: .
dockerfile: zabbixagent.dockerfile
environment:
ZBX_HOSTNAME: OnvifAgent plugin
ZBX_SERVER_HOST: server
ZBX_SERVER_PORT: 10051
volumes:
- ./log:/var/log/onvif-agent
- ./config.yaml:/etc/onvif-agent/config.yaml
networks:
- zabbix-network
restart: unless-stopped
networks:
zabbix-network:
external: true

View File

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

View File

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

View File

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

2
go.mod
View File

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

View File

@ -1,5 +1,5 @@
package zabbixagent
const (
PluginName = "OnvifAgent"
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"
"io"
"net/http"
"onvif-agent/config"
"onvif-agent/constant"
"strings"
"zabbixagent2plugintemplate/config"
"zabbixagent2plugintemplate/constant"
)
// 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) {
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) {
@ -35,12 +35,12 @@ func (h *Handler) HTTPClient(ctx context.Context, params map[string]string, _ ..
url := params["url"]
if !strings.HasPrefix(url, "http") {
url = fmt.Sprintf("http://localhost:%d/%s", config.Config.App.Port, url)
url = fmt.Sprintf("http://localhost:%d%s", config.Config.App.Port, url)
}
body := params["body"]
req, err := http.NewRequestWithContext(ctx, method, "https://api.imbytecat.com/ip", strings.NewReader(body)) // TODO: if empty use http.NoBody
req, err := http.NewRequestWithContext(ctx, method, url, strings.NewReader(body)) // TODO: if empty use http.NoBody
if err != nil {
return nil, errs.Wrapf(err, "failed to create request")
}

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)
}

View File

@ -76,17 +76,17 @@ func (p *zabbixAgentPlugin) registerMetrics() error {
h := NewHandler()
p.metrics = map[metricKey]*metricBinding{
"onvif.version": {
"onvif.app.version": {
metric: metric.New(
"ONVIF app version",
"App version",
nil,
false,
),
handler: h.GetAppVersion,
},
"onvif.client": {
"onvif.httpclient": {
metric: metric.New(
"ONVIF client",
"ONVIF HTTP client",
[]*metric.Param{
metric.NewParam("method", "HTTP request method."),
metric.NewParam("url", "HTTP request URL."),

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
}

10
router/handler/hello.go Normal file
View File

@ -0,0 +1,10 @@
package handler
import (
"github.com/gin-gonic/gin"
"zabbixagent2plugintemplate/response"
)
func Hello(c *gin.Context) {
response.NewResponse().Success("Hello, World!").Send(c)
}

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"
"log"
"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")
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]
if conn == nil {
c.JSON(http.StatusNotFound, gin.H{
"message": "Connection not found",
})
session := onvif.Sessions[xaddr]
if session == nil {
response.NewResponse().Fail("Connection not found").WithCode(http.StatusNotFound).Send(c)
return
}
result, err := conn.SubscribeEvents(callbackURL, "PT60S")
result, err := session.EventSubscribe(callbackURL, "PT60S")
if err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{
"message": err.Error(),
})
response.NewResponse().Error(err).Send(c)
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")
log.Printf("EventNotifyCallback from: %s", xaddr)
log.Printf("NotifyCallback xaddr: %s", xaddr)
var notify event.Notify
envelope := gosoap.NewSOAPEnvelope(&notify)
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
}
// TODO: handle notifications
for _, msg := range envelope.Body.Content.(*event.Notify).NotificationMessage {
log.Printf("Topic: %s, Message: %s", msg.Topic.TopicKinds, msg.Message.Message)
}
onvif.Notifications[xaddr] = append(onvif.Notifications[xaddr], notify.NotificationMessage...)
c.JSON(http.StatusOK, gin.H{"message": "OK"})
response.NewResponse().Success().Send(c)
}

View File

@ -1,11 +0,0 @@
package handler
import (
"github.com/gin-gonic/gin"
)
func Ping(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
}

View File

@ -2,27 +2,42 @@ package router
import (
"github.com/gin-gonic/gin"
"onvif-agent/router/handler"
"onvif-agent/router/handler/onvif"
"zabbixagent2plugintemplate/integration/zabbixagent"
"zabbixagent2plugintemplate/router/handler"
"zabbixagent2plugintemplate/router/handler/onvif"
)
func SetupRoutes(r *gin.Engine) {
r.GET("/ping", handler.Ping)
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.GET("/", onvif.GetConnections)
connectionGroup.GET("/:xaddr", onvif.GetConnectionByXaddr)
connectionGroup.DELETE("/:xaddr", onvif.DeleteConnection)
connectionGroup.POST("/", onvif.CreateSession)
connectionGroup.GET("/", onvif.GetSessions)
connectionGroup.GET("/:xaddr", onvif.GetSessionByXaddr)
connectionGroup.DELETE("/:xaddr", onvif.DeleteSessionByXaddr)
}
subscriptionGroup := userGroup.Group("/subscriptions")
subscriptionGroup := onvifGroup.Group("/subscriptions")
{
subscriptionGroup.POST("/:xaddr", onvif.CreateEventSubscription)
subscriptionGroup.POST("/:xaddr/callback", onvif.EventNotifyCallback)
subscriptionGroup.POST("/:xaddr", onvif.CreateSubscription)
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"
func (c *Connection) GetDeviceInfo() (*device.GetDeviceInformationResponse, error) {
func (c *Session) GetDeviceInfo() (*device.GetDeviceInformationResponse, error) {
resp, err := c.Device.CallMethod(device.GetDeviceInformation{})
if err != nil {
return nil, err

View File

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

View File

@ -5,16 +5,18 @@ import (
"strings"
)
type Connection struct {
type Session struct {
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, ":") {
xaddr += ":80"
}
dev, err := onvif.NewDevice(onvif.DeviceParams{
Xaddr: xaddr,
Username: username,
@ -25,5 +27,5 @@ func New(xaddr string, username string, password string) (*Connection, error) {
return nil, err
}
return &Connection{Device: dev}, nil
return &Session{Device: dev}, nil
}

View File

@ -1,9 +0,0 @@
FROM golang:1.23 AS builder
WORKDIR /build
COPY . ./
RUN GOPROXY=https://goproxy.cn go mod download
RUN PROFILE=dev CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o out/app
FROM zabbix/zabbix-agent2:ubuntu-7.0-latest
COPY --from=builder /build/out/app /usr/sbin/onvif-agent
RUN echo "Plugins.Onvif.System.Path=/usr/sbin/onvif-agent" >> /etc/zabbix/zabbix_agent2.d/plugins.d/onvif.conf