Compare commits
57 Commits
554da113e3
...
main
Author | SHA1 | Date | |
---|---|---|---|
f709807ce7 | |||
207001c1f8 | |||
43ffa61470 | |||
0b7b5bc54e | |||
78ae4ca00b | |||
e67a294ff6 | |||
ee5efb0f5a | |||
87da521e2e | |||
d39363c8a1 | |||
b90db27e15 | |||
8b7e7a3f85 | |||
3085195290 | |||
ce77821c2b | |||
6c98a35488 | |||
99c9303517 | |||
4262b1aaea | |||
e28fd1a0b9 | |||
f4c8b5ed21 | |||
f2c5cb5f8d | |||
c2683ab03f | |||
6539495e4d | |||
8871ebeb41 | |||
4fbdbb15cc | |||
b1ad068073 | |||
254b41e160 | |||
c8b33a27f6 | |||
ae9fd0d5b0 | |||
2bd63388cc | |||
a641d6846d | |||
67de79e230 | |||
0ef8af021f | |||
ff5cc66a55 | |||
63fb12901b | |||
219116c961 | |||
de3667f957 | |||
82d3f99404 | |||
a6cc98a32b | |||
2a1852188e | |||
7eee4cfc26 | |||
00d2b30658 | |||
0493c3b1e6 | |||
74c955a3f7 | |||
85c90ec1e1 | |||
efae826049 | |||
b5c1ec3bda | |||
8992b65e76 | |||
228ec26904 | |||
0f4c565250 | |||
d82a83cca1 | |||
09d4d305cd | |||
d4bc25fa59 | |||
fcce8f78b0 | |||
a417edcfed | |||
77d8153b43 | |||
28482d201d | |||
6294a2ea3a | |||
1e2a8fb6c7 |
51
.gitea/workflows/template-cleanup.yaml
Normal file
51
.gitea/workflows/template-cleanup.yaml
Normal 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
138
.gitignore
vendored
@ -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
8
.idea/.gitignore
generated
vendored
@ -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
|
6
.idea/git_toolbox_blame.xml
generated
6
.idea/git_toolbox_blame.xml
generated
@ -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
8
.idea/modules.xml
generated
@ -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
13
.idea/onvif-agent.iml
generated
@ -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
6
.idea/vcs.xml
generated
@ -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
9
Dockerfile
Normal 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
|
19
README.md
19
README.md
@ -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`.
|
||||
|
@ -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
19
compose.yaml
Normal 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
|
@ -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
|
@ -1,4 +1,4 @@
|
||||
app:
|
||||
port: 8080
|
||||
host: "localhost"
|
||||
url: "http://localhost:8080"
|
||||
host: "0.0.0.0"
|
||||
url: "http://zabbixagent2plugintemplate:8080"
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"github.com/spf13/viper"
|
||||
"log"
|
||||
"onvif-agent/constant"
|
||||
"os"
|
||||
"zabbixagent2plugintemplate/constant"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
AppName = "onvif-agent"
|
||||
AppName = "zabbixagent2plugintemplate"
|
||||
AppVersion = "1.0.0"
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
package zabbixagent
|
||||
|
||||
const (
|
||||
PluginName = "OnvifAgent"
|
||||
PluginName = "Zabbixagent2plugintemplate"
|
||||
)
|
||||
|
72
integration/zabbixagent/gin_handler.go
Normal file
72
integration/zabbixagent/gin_handler.go
Normal 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)
|
||||
}
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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
73
response/response.go
Normal 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
10
router/handler/hello.go
Normal 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)
|
||||
}
|
@ -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",
|
||||
})
|
||||
}
|
23
router/handler/onvif/notification.go
Normal file
23
router/handler/onvif/notification.go
Normal 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)
|
||||
}
|
79
router/handler/onvif/session.go
Normal file
79
router/handler/onvif/session.go
Normal 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)
|
||||
}
|
@ -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(¬ify)
|
||||
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)
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Ping(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"message": "pong",
|
||||
})
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
@ -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
|
Reference in New Issue
Block a user