chore: sync local updates
This commit is contained in:
9
mengyamonitor-backend-server/.dockerignore
Normal file
9
mengyamonitor-backend-server/.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
||||
data/
|
||||
.git
|
||||
.gitignore
|
||||
*.md
|
||||
.env
|
||||
.env.*
|
||||
*.exe
|
||||
mengyamonitor-backend-server
|
||||
__debug_bin
|
||||
21
mengyamonitor-backend-server/Dockerfile
Normal file
21
mengyamonitor-backend-server/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
# 中心服务:HTTP/WebSocket + gRPC(纯 Go / modernc SQLite,CGO_ENABLED=0)
|
||||
# 镜像内进程为明文 HTTP;HTTPS 请在宿主机 Nginx 等反代层配置,内网直连本端口即可。
|
||||
FROM golang:1.22-alpine AS build
|
||||
WORKDIR /src
|
||||
RUN apk add --no-cache git
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /central .
|
||||
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
WORKDIR /app
|
||||
# 与 config 默认一致; compose 中可覆盖
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=9393
|
||||
ENV GRPC_PORT=9394
|
||||
ENV DB_PATH=/app/data/servers.db
|
||||
COPY --from=build /central /app/central
|
||||
EXPOSE 9393 9394
|
||||
ENTRYPOINT ["/app/central"]
|
||||
13
mengyamonitor-backend-server/Dockerfile.binary
Normal file
13
mengyamonitor-backend-server/Dockerfile.binary
Normal file
@@ -0,0 +1,13 @@
|
||||
# 使用本机/CI 已编译好的 Linux amd64 二进制打包镜像(不在 Docker 内 go build)
|
||||
# 先执行: build-linux-amd64.bat 或 release 目录放置同名文件
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
WORKDIR /app
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=9393
|
||||
ENV GRPC_PORT=9394
|
||||
ENV DB_PATH=/app/data/servers.db
|
||||
COPY release/mengyamonitor-central-linux-amd64 /app/central
|
||||
RUN chmod +x /app/central
|
||||
EXPOSE 9393 9394
|
||||
ENTRYPOINT ["/app/central"]
|
||||
28
mengyamonitor-backend-server/build-linux-amd64.bat
Normal file
28
mengyamonitor-backend-server/build-linux-amd64.bat
Normal file
@@ -0,0 +1,28 @@
|
||||
@echo off
|
||||
setlocal
|
||||
cd /d "%~dp0"
|
||||
|
||||
where go >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [错误] 未找到 go,请先安装 Go 并加入 PATH
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist release mkdir release
|
||||
|
||||
set GOOS=linux
|
||||
set GOARCH=amd64
|
||||
set CGO_ENABLED=0
|
||||
|
||||
echo 正在编译 Linux amd64 release\mengyamonitor-central-linux-amd64 ...
|
||||
go build -trimpath -ldflags="-s -w" -o release\mengyamonitor-central-linux-amd64 .
|
||||
if errorlevel 1 (
|
||||
echo [错误] go build 失败
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo 完成: %cd%\release\mengyamonitor-central-linux-amd64
|
||||
echo.
|
||||
echo 可选:用预编译镜像启动(不再在 Docker 里编译)
|
||||
echo docker compose -f docker-compose.binary.yml up -d --build
|
||||
exit /b 0
|
||||
20
mengyamonitor-backend-server/docker-compose.binary.yml
Normal file
20
mengyamonitor-backend-server/docker-compose.binary.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
# 使用 release/mengyamonitor-central-linux-amd64 打包镜像(先在 Windows 运行 build-linux-amd64.bat)
|
||||
# 容器内仍是 HTTP:9393 / 9394;HTTPS 仅公网 Nginx 反代层
|
||||
services:
|
||||
central:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.binary
|
||||
image: mengyamonitor-central:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9393:9393"
|
||||
- "9394:9394"
|
||||
environment:
|
||||
HOST: "0.0.0.0"
|
||||
PORT: "9393"
|
||||
GRPC_PORT: "9394"
|
||||
ADMIN_TOKEN: ${ADMIN_TOKEN:-shumengya520}
|
||||
DB_PATH: /app/data/servers.db
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
23
mengyamonitor-backend-server/docker-compose.yml
Normal file
23
mengyamonitor-backend-server/docker-compose.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
# 中心服务在容器内仅为 HTTP(9393 Web/WS、9394 gRPC);TLS 由公网 Nginx 做,与内网无关。
|
||||
#
|
||||
# 本文件:在 Docker 内完整执行 go build(适合无本机 Go 或 CI)。
|
||||
# 若已在 Windows 编好 Linux 二进制,避免镜像内再编译:先运行 build-linux-amd64.bat,再
|
||||
# docker compose -f docker-compose.binary.yml up -d --build
|
||||
services:
|
||||
central:
|
||||
build: .
|
||||
image: mengyamonitor-central:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# 宿主:容器 —— 可按需改为 "127.0.0.1:9393:9393" 仅本机反代
|
||||
- "9393:9393"
|
||||
- "9394:9394"
|
||||
environment:
|
||||
HOST: "0.0.0.0"
|
||||
PORT: "9393"
|
||||
GRPC_PORT: "9394"
|
||||
# 务必在宿主设置强口令:export ADMIN_TOKEN=... 或下方 env_file
|
||||
ADMIN_TOKEN: ${ADMIN_TOKEN:-shumengya520}
|
||||
DB_PATH: /app/data/servers.db
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
47
mengyamonitor-backend-server/go.mod
Normal file
47
mengyamonitor-backend-server/go.mod
Normal file
@@ -0,0 +1,47 @@
|
||||
module mengyamonitor-backend-server
|
||||
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
google.golang.org/grpc v1.70.0
|
||||
google.golang.org/protobuf v1.35.2
|
||||
modernc.org/sqlite v1.34.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.30.0 // indirect
|
||||
golang.org/x/net v0.32.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.55.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
)
|
||||
149
mengyamonitor-backend-server/go.sum
Normal file
149
mengyamonitor-backend-server/go.sum
Normal file
@@ -0,0 +1,149 @@
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
|
||||
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
|
||||
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
||||
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
|
||||
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
||||
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
|
||||
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g=
|
||||
modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
30
mengyamonitor-backend-server/internal/config/config.go
Normal file
30
mengyamonitor-backend-server/internal/config/config.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Host string
|
||||
Port string
|
||||
GRPCPort string
|
||||
AdminToken string
|
||||
DBPath string
|
||||
}
|
||||
|
||||
func Load() *Config {
|
||||
return &Config{
|
||||
Host: getenv("HOST", "0.0.0.0"),
|
||||
Port: getenv("PORT", "9393"),
|
||||
GRPCPort: getenv("GRPC_PORT", "9394"),
|
||||
AdminToken: getenv("ADMIN_TOKEN", "shumengya520"),
|
||||
DBPath: getenv("DB_PATH", "./data/servers.db"),
|
||||
}
|
||||
}
|
||||
|
||||
func getenv(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
492
mengyamonitor-backend-server/internal/gen/mengyav1/agent.pb.go
Normal file
492
mengyamonitor-backend-server/internal/gen/mengyav1/agent.pb.go
Normal file
@@ -0,0 +1,492 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.5
|
||||
// protoc v5.28.3
|
||||
// source: mengya/v1/agent.proto
|
||||
|
||||
package mengyav1
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type AgentMessage struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Payload:
|
||||
//
|
||||
// *AgentMessage_Hello
|
||||
// *AgentMessage_Heartbeat
|
||||
// *AgentMessage_Metrics
|
||||
Payload isAgentMessage_Payload `protobuf_oneof:"payload"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *AgentMessage) Reset() {
|
||||
*x = AgentMessage{}
|
||||
mi := &file_mengya_v1_agent_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *AgentMessage) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AgentMessage) ProtoMessage() {}
|
||||
|
||||
func (x *AgentMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mengya_v1_agent_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AgentMessage.ProtoReflect.Descriptor instead.
|
||||
func (*AgentMessage) Descriptor() ([]byte, []int) {
|
||||
return file_mengya_v1_agent_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *AgentMessage) GetPayload() isAgentMessage_Payload {
|
||||
if x != nil {
|
||||
return x.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AgentMessage) GetHello() *Hello {
|
||||
if x != nil {
|
||||
if x, ok := x.Payload.(*AgentMessage_Hello); ok {
|
||||
return x.Hello
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AgentMessage) GetHeartbeat() *Heartbeat {
|
||||
if x != nil {
|
||||
if x, ok := x.Payload.(*AgentMessage_Heartbeat); ok {
|
||||
return x.Heartbeat
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AgentMessage) GetMetrics() *MetricsReport {
|
||||
if x != nil {
|
||||
if x, ok := x.Payload.(*AgentMessage_Metrics); ok {
|
||||
return x.Metrics
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isAgentMessage_Payload interface {
|
||||
isAgentMessage_Payload()
|
||||
}
|
||||
|
||||
type AgentMessage_Hello struct {
|
||||
Hello *Hello `protobuf:"bytes,1,opt,name=hello,proto3,oneof"`
|
||||
}
|
||||
|
||||
type AgentMessage_Heartbeat struct {
|
||||
Heartbeat *Heartbeat `protobuf:"bytes,2,opt,name=heartbeat,proto3,oneof"`
|
||||
}
|
||||
|
||||
type AgentMessage_Metrics struct {
|
||||
Metrics *MetricsReport `protobuf:"bytes,3,opt,name=metrics,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*AgentMessage_Hello) isAgentMessage_Payload() {}
|
||||
|
||||
func (*AgentMessage_Heartbeat) isAgentMessage_Payload() {}
|
||||
|
||||
func (*AgentMessage_Metrics) isAgentMessage_Payload() {}
|
||||
|
||||
type Hello struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ServerId string `protobuf:"bytes,1,opt,name=server_id,json=serverId,proto3" json:"server_id,omitempty"`
|
||||
AgentKey string `protobuf:"bytes,2,opt,name=agent_key,json=agentKey,proto3" json:"agent_key,omitempty"`
|
||||
Hostname string `protobuf:"bytes,3,opt,name=hostname,proto3" json:"hostname,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Hello) Reset() {
|
||||
*x = Hello{}
|
||||
mi := &file_mengya_v1_agent_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Hello) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Hello) ProtoMessage() {}
|
||||
|
||||
func (x *Hello) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mengya_v1_agent_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Hello.ProtoReflect.Descriptor instead.
|
||||
func (*Hello) Descriptor() ([]byte, []int) {
|
||||
return file_mengya_v1_agent_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Hello) GetServerId() string {
|
||||
if x != nil {
|
||||
return x.ServerId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Hello) GetAgentKey() string {
|
||||
if x != nil {
|
||||
return x.AgentKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Hello) GetHostname() string {
|
||||
if x != nil {
|
||||
return x.Hostname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Heartbeat struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Heartbeat) Reset() {
|
||||
*x = Heartbeat{}
|
||||
mi := &file_mengya_v1_agent_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Heartbeat) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Heartbeat) ProtoMessage() {}
|
||||
|
||||
func (x *Heartbeat) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mengya_v1_agent_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Heartbeat.ProtoReflect.Descriptor instead.
|
||||
func (*Heartbeat) Descriptor() ([]byte, []int) {
|
||||
return file_mengya_v1_agent_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
type MetricsReport struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
PayloadJson string `protobuf:"bytes,1,opt,name=payload_json,json=payloadJson,proto3" json:"payload_json,omitempty"`
|
||||
CollectedAtUnixMs int64 `protobuf:"varint,2,opt,name=collected_at_unix_ms,json=collectedAtUnixMs,proto3" json:"collected_at_unix_ms,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *MetricsReport) Reset() {
|
||||
*x = MetricsReport{}
|
||||
mi := &file_mengya_v1_agent_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *MetricsReport) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MetricsReport) ProtoMessage() {}
|
||||
|
||||
func (x *MetricsReport) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mengya_v1_agent_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use MetricsReport.ProtoReflect.Descriptor instead.
|
||||
func (*MetricsReport) Descriptor() ([]byte, []int) {
|
||||
return file_mengya_v1_agent_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *MetricsReport) GetPayloadJson() string {
|
||||
if x != nil {
|
||||
return x.PayloadJson
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *MetricsReport) GetCollectedAtUnixMs() int64 {
|
||||
if x != nil {
|
||||
return x.CollectedAtUnixMs
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ControlMessage struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Payload:
|
||||
//
|
||||
// *ControlMessage_Ack
|
||||
Payload isControlMessage_Payload `protobuf_oneof:"payload"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ControlMessage) Reset() {
|
||||
*x = ControlMessage{}
|
||||
mi := &file_mengya_v1_agent_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ControlMessage) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ControlMessage) ProtoMessage() {}
|
||||
|
||||
func (x *ControlMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mengya_v1_agent_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ControlMessage.ProtoReflect.Descriptor instead.
|
||||
func (*ControlMessage) Descriptor() ([]byte, []int) {
|
||||
return file_mengya_v1_agent_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *ControlMessage) GetPayload() isControlMessage_Payload {
|
||||
if x != nil {
|
||||
return x.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ControlMessage) GetAck() *Ack {
|
||||
if x != nil {
|
||||
if x, ok := x.Payload.(*ControlMessage_Ack); ok {
|
||||
return x.Ack
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isControlMessage_Payload interface {
|
||||
isControlMessage_Payload()
|
||||
}
|
||||
|
||||
type ControlMessage_Ack struct {
|
||||
Ack *Ack `protobuf:"bytes,1,opt,name=ack,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*ControlMessage_Ack) isControlMessage_Payload() {}
|
||||
|
||||
type Ack struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Ack) Reset() {
|
||||
*x = Ack{}
|
||||
mi := &file_mengya_v1_agent_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Ack) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Ack) ProtoMessage() {}
|
||||
|
||||
func (x *Ack) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mengya_v1_agent_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Ack.ProtoReflect.Descriptor instead.
|
||||
func (*Ack) Descriptor() ([]byte, []int) {
|
||||
return file_mengya_v1_agent_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *Ack) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_mengya_v1_agent_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_mengya_v1_agent_proto_rawDesc = string([]byte{
|
||||
0x0a, 0x15, 0x6d, 0x65, 0x6e, 0x67, 0x79, 0x61, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x67, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x6d, 0x65, 0x6e, 0x67, 0x79, 0x61, 0x2e,
|
||||
0x76, 0x31, 0x22, 0xaf, 0x01, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73,
|
||||
0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6d, 0x65, 0x6e, 0x67, 0x79, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x48,
|
||||
0x65, 0x6c, 0x6c, 0x6f, 0x48, 0x00, 0x52, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x34, 0x0a,
|
||||
0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x14, 0x2e, 0x6d, 0x65, 0x6e, 0x67, 0x79, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61,
|
||||
0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x48, 0x00, 0x52, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62,
|
||||
0x65, 0x61, 0x74, 0x12, 0x34, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x65, 0x6e, 0x67, 0x79, 0x61, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00,
|
||||
0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79,
|
||||
0x6c, 0x6f, 0x61, 0x64, 0x22, 0x5d, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x1b, 0x0a,
|
||||
0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61,
|
||||
0x67, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74,
|
||||
0x22, 0x63, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x70, 0x6f, 0x72,
|
||||
0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x6a, 0x73, 0x6f,
|
||||
0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
||||
0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x2f, 0x0a, 0x14, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65,
|
||||
0x64, 0x5f, 0x61, 0x74, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x11, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x41, 0x74, 0x55,
|
||||
0x6e, 0x69, 0x78, 0x4d, 0x73, 0x22, 0x3f, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
|
||||
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x22, 0x0a, 0x03, 0x61, 0x63, 0x6b, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6d, 0x65, 0x6e, 0x67, 0x79, 0x61, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x41, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x03, 0x61, 0x63, 0x6b, 0x42, 0x09, 0x0a, 0x07, 0x70,
|
||||
0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x1f, 0x0a, 0x03, 0x41, 0x63, 0x6b, 0x12, 0x18, 0x0a,
|
||||
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x50, 0x0a, 0x0b, 0x41, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x12, 0x41, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
|
||||
0x74, 0x12, 0x17, 0x2e, 0x6d, 0x65, 0x6e, 0x67, 0x79, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x19, 0x2e, 0x6d, 0x65, 0x6e,
|
||||
0x67, 0x79, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x4d, 0x65,
|
||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x3d, 0x5a, 0x3b, 0x6d, 0x65, 0x6e,
|
||||
0x67, 0x79, 0x61, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x2d, 0x62, 0x61, 0x63, 0x6b, 0x65,
|
||||
0x6e, 0x64, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
||||
0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6d, 0x65, 0x6e, 0x67, 0x79, 0x61, 0x76, 0x31, 0x3b,
|
||||
0x6d, 0x65, 0x6e, 0x67, 0x79, 0x61, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
})
|
||||
|
||||
var (
|
||||
file_mengya_v1_agent_proto_rawDescOnce sync.Once
|
||||
file_mengya_v1_agent_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_mengya_v1_agent_proto_rawDescGZIP() []byte {
|
||||
file_mengya_v1_agent_proto_rawDescOnce.Do(func() {
|
||||
file_mengya_v1_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_mengya_v1_agent_proto_rawDesc), len(file_mengya_v1_agent_proto_rawDesc)))
|
||||
})
|
||||
return file_mengya_v1_agent_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_mengya_v1_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
||||
var file_mengya_v1_agent_proto_goTypes = []any{
|
||||
(*AgentMessage)(nil), // 0: mengya.v1.AgentMessage
|
||||
(*Hello)(nil), // 1: mengya.v1.Hello
|
||||
(*Heartbeat)(nil), // 2: mengya.v1.Heartbeat
|
||||
(*MetricsReport)(nil), // 3: mengya.v1.MetricsReport
|
||||
(*ControlMessage)(nil), // 4: mengya.v1.ControlMessage
|
||||
(*Ack)(nil), // 5: mengya.v1.Ack
|
||||
}
|
||||
var file_mengya_v1_agent_proto_depIdxs = []int32{
|
||||
1, // 0: mengya.v1.AgentMessage.hello:type_name -> mengya.v1.Hello
|
||||
2, // 1: mengya.v1.AgentMessage.heartbeat:type_name -> mengya.v1.Heartbeat
|
||||
3, // 2: mengya.v1.AgentMessage.metrics:type_name -> mengya.v1.MetricsReport
|
||||
5, // 3: mengya.v1.ControlMessage.ack:type_name -> mengya.v1.Ack
|
||||
0, // 4: mengya.v1.AgentIngest.Connect:input_type -> mengya.v1.AgentMessage
|
||||
4, // 5: mengya.v1.AgentIngest.Connect:output_type -> mengya.v1.ControlMessage
|
||||
5, // [5:6] is the sub-list for method output_type
|
||||
4, // [4:5] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_mengya_v1_agent_proto_init() }
|
||||
func file_mengya_v1_agent_proto_init() {
|
||||
if File_mengya_v1_agent_proto != nil {
|
||||
return
|
||||
}
|
||||
file_mengya_v1_agent_proto_msgTypes[0].OneofWrappers = []any{
|
||||
(*AgentMessage_Hello)(nil),
|
||||
(*AgentMessage_Heartbeat)(nil),
|
||||
(*AgentMessage_Metrics)(nil),
|
||||
}
|
||||
file_mengya_v1_agent_proto_msgTypes[4].OneofWrappers = []any{
|
||||
(*ControlMessage_Ack)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_mengya_v1_agent_proto_rawDesc), len(file_mengya_v1_agent_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 6,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_mengya_v1_agent_proto_goTypes,
|
||||
DependencyIndexes: file_mengya_v1_agent_proto_depIdxs,
|
||||
MessageInfos: file_mengya_v1_agent_proto_msgTypes,
|
||||
}.Build()
|
||||
File_mengya_v1_agent_proto = out.File
|
||||
file_mengya_v1_agent_proto_goTypes = nil
|
||||
file_mengya_v1_agent_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.28.3
|
||||
// source: mengya/v1/agent.proto
|
||||
|
||||
package mengyav1
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
AgentIngest_Connect_FullMethodName = "/mengya.v1.AgentIngest/Connect"
|
||||
)
|
||||
|
||||
// AgentIngestClient is the client API for AgentIngest service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
//
|
||||
// AgentIngest: 采集端出站连接,双向流上报指标。
|
||||
type AgentIngestClient interface {
|
||||
Connect(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[AgentMessage, ControlMessage], error)
|
||||
}
|
||||
|
||||
type agentIngestClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewAgentIngestClient(cc grpc.ClientConnInterface) AgentIngestClient {
|
||||
return &agentIngestClient{cc}
|
||||
}
|
||||
|
||||
func (c *agentIngestClient) Connect(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[AgentMessage, ControlMessage], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &AgentIngest_ServiceDesc.Streams[0], AgentIngest_Connect_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[AgentMessage, ControlMessage]{ClientStream: stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type AgentIngest_ConnectClient = grpc.BidiStreamingClient[AgentMessage, ControlMessage]
|
||||
|
||||
// AgentIngestServer is the server API for AgentIngest service.
|
||||
// All implementations must embed UnimplementedAgentIngestServer
|
||||
// for forward compatibility.
|
||||
//
|
||||
// AgentIngest: 采集端出站连接,双向流上报指标。
|
||||
type AgentIngestServer interface {
|
||||
Connect(grpc.BidiStreamingServer[AgentMessage, ControlMessage]) error
|
||||
mustEmbedUnimplementedAgentIngestServer()
|
||||
}
|
||||
|
||||
// UnimplementedAgentIngestServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedAgentIngestServer struct{}
|
||||
|
||||
func (UnimplementedAgentIngestServer) Connect(grpc.BidiStreamingServer[AgentMessage, ControlMessage]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method Connect not implemented")
|
||||
}
|
||||
func (UnimplementedAgentIngestServer) mustEmbedUnimplementedAgentIngestServer() {}
|
||||
func (UnimplementedAgentIngestServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeAgentIngestServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to AgentIngestServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeAgentIngestServer interface {
|
||||
mustEmbedUnimplementedAgentIngestServer()
|
||||
}
|
||||
|
||||
func RegisterAgentIngestServer(s grpc.ServiceRegistrar, srv AgentIngestServer) {
|
||||
// If the following call pancis, it indicates UnimplementedAgentIngestServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&AgentIngest_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _AgentIngest_Connect_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(AgentIngestServer).Connect(&grpc.GenericServerStream[AgentMessage, ControlMessage]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type AgentIngest_ConnectServer = grpc.BidiStreamingServer[AgentMessage, ControlMessage]
|
||||
|
||||
// AgentIngest_ServiceDesc is the grpc.ServiceDesc for AgentIngest service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var AgentIngest_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "mengya.v1.AgentIngest",
|
||||
HandlerType: (*AgentIngestServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "Connect",
|
||||
Handler: _AgentIngest_Connect_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "mengya/v1/agent.proto",
|
||||
}
|
||||
22
mengyamonitor-backend-server/internal/grpcrun/grpcrun.go
Normal file
22
mengyamonitor-backend-server/internal/grpcrun/grpcrun.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package grpcrun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"mengyamonitor-backend-server/internal/gen/mengyav1"
|
||||
"mengyamonitor-backend-server/internal/ingest"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Serve starts a blocking gRPC server for agent ingest.
|
||||
func Serve(listenAddr string, srv *ingest.Server) error {
|
||||
lis, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen: %w", err)
|
||||
}
|
||||
g := grpc.NewServer()
|
||||
mengyav1.RegisterAgentIngestServer(g, srv)
|
||||
return g.Serve(lis)
|
||||
}
|
||||
100
mengyamonitor-backend-server/internal/handler/admin.go
Normal file
100
mengyamonitor-backend-server/internal/handler/admin.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"mengyamonitor-backend-server/internal/config"
|
||||
"mengyamonitor-backend-server/internal/model"
|
||||
"mengyamonitor-backend-server/internal/store"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Admin struct {
|
||||
Store *store.Store
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
func (h *Admin) Auth(c *gin.Context) {
|
||||
var in model.AuthInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body"})
|
||||
return
|
||||
}
|
||||
if subtle.ConstantTimeCompare([]byte(in.Token), []byte(h.Cfg.AdminToken)) != 1 {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
func (h *Admin) ListServers(c *gin.Context) {
|
||||
list, err := h.Store.ListAll(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": list})
|
||||
}
|
||||
|
||||
func (h *Admin) CreateServer(c *gin.Context) {
|
||||
var in model.CreateServerInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body"})
|
||||
return
|
||||
}
|
||||
s, err := h.Store.Create(c.Request.Context(), in)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, gin.H{"data": s})
|
||||
}
|
||||
|
||||
func (h *Admin) UpdateServer(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var in model.UpdateServerInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body"})
|
||||
return
|
||||
}
|
||||
s, err := h.Store.Update(c.Request.Context(), id, in)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": s})
|
||||
}
|
||||
|
||||
func (h *Admin) DeleteServer(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if err := h.Store.Delete(c.Request.Context(), id); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
func (h *Admin) Reorder(c *gin.Context) {
|
||||
var in model.ReorderInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body"})
|
||||
return
|
||||
}
|
||||
if err := h.Store.Reorder(c.Request.Context(), in.IDs); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
70
mengyamonitor-backend-server/internal/handler/public.go
Normal file
70
mengyamonitor-backend-server/internal/handler/public.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"mengyamonitor-backend-server/internal/model"
|
||||
"mengyamonitor-backend-server/internal/store"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Public struct {
|
||||
Store *store.Store
|
||||
}
|
||||
|
||||
// publicServer hides agent_key from unauthenticated API consumers.
|
||||
type publicServer struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Enabled bool `json:"enabled"`
|
||||
SortOrder int `json:"sortOrder"`
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
}
|
||||
|
||||
func toPublicServers(in []model.MonitoredServer) []publicServer {
|
||||
out := make([]publicServer, 0, len(in))
|
||||
for _, s := range in {
|
||||
out = append(out, publicServer{
|
||||
ID: s.ID, Name: s.Name, URL: s.URL, Enabled: s.Enabled,
|
||||
SortOrder: s.SortOrder, CreatedAt: s.CreatedAt,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (h *Public) Health(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||||
}
|
||||
|
||||
func (h *Public) ListServers(c *gin.Context) {
|
||||
list, err := h.Store.ListEnabled(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": toPublicServers(list)})
|
||||
}
|
||||
|
||||
func (h *Public) Root(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"service": "萌芽监控 — 中心服务",
|
||||
"version": "1.0.0",
|
||||
"public": []string{
|
||||
"GET /api/health",
|
||||
"GET /api/servers",
|
||||
"GET /api/ws/monitor — WebSocket 指标快照",
|
||||
"gRPC AgentIngest — 见环境变量 GRPC_PORT",
|
||||
},
|
||||
"admin": []string{
|
||||
"POST /api/admin/auth",
|
||||
"GET /api/admin/servers",
|
||||
"POST /api/admin/servers",
|
||||
"PUT /api/admin/servers/:id",
|
||||
"DELETE /api/admin/servers/:id",
|
||||
"PUT /api/admin/servers/reorder",
|
||||
},
|
||||
})
|
||||
}
|
||||
50
mengyamonitor-backend-server/internal/handler/ws.go
Normal file
50
mengyamonitor-backend-server/internal/handler/ws.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"mengyamonitor-backend-server/internal/hub"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var wsUpgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// MonitorWebSocket upgrades to WebSocket and streams hub snapshots.
|
||||
func MonitorWebSocket(h *hub.Hub) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
conn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
log.Printf("ws: upgrade: %v", err)
|
||||
return
|
||||
}
|
||||
defer h.Unregister(conn)
|
||||
snap, err := h.BuildSnapshotMessage()
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
_ = conn.SetWriteDeadline(time.Now().Add(15 * time.Second))
|
||||
if err := conn.WriteMessage(websocket.TextMessage, snap); err != nil {
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
h.AddSubscriber(conn)
|
||||
for {
|
||||
_ = conn.SetReadDeadline(time.Now().Add(120 * time.Second))
|
||||
_, _, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
108
mengyamonitor-backend-server/internal/hub/hub.go
Normal file
108
mengyamonitor-backend-server/internal/hub/hub.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// WSMessage is sent to browsers (matches frontend ServerStatus shape).
|
||||
type WSMessage struct {
|
||||
Type string `json:"type"`
|
||||
Data map[string]json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
type Hub struct {
|
||||
mu sync.RWMutex
|
||||
// serverId -> latest metrics JSON object (not wrapped)
|
||||
metrics map[string]json.RawMessage
|
||||
|
||||
subsMu sync.Mutex
|
||||
// subscribers for broadcast
|
||||
conns map[*websocket.Conn]struct{}
|
||||
}
|
||||
|
||||
func New() *Hub {
|
||||
return &Hub{
|
||||
metrics: make(map[string]json.RawMessage),
|
||||
conns: make(map[*websocket.Conn]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Hub) SetMetrics(serverID string, metricsJSON []byte) {
|
||||
h.mu.Lock()
|
||||
if len(metricsJSON) == 0 {
|
||||
delete(h.metrics, serverID)
|
||||
} else {
|
||||
h.metrics[serverID] = json.RawMessage(append([]byte(nil), metricsJSON...))
|
||||
}
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
func (h *Hub) snapshotData() map[string]json.RawMessage {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
out := make(map[string]json.RawMessage, len(h.metrics))
|
||||
now := time.Now().UnixMilli()
|
||||
for id, raw := range h.metrics {
|
||||
// Wrap as ServerStatus for frontend compatibility
|
||||
wrap := map[string]any{
|
||||
"serverId": id,
|
||||
"online": true,
|
||||
"metrics": json.RawMessage(raw),
|
||||
"lastUpdate": now,
|
||||
}
|
||||
b, err := json.Marshal(wrap)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out[id] = b
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (h *Hub) BuildSnapshotMessage() ([]byte, error) {
|
||||
msg := WSMessage{
|
||||
Type: "snapshot",
|
||||
Data: h.snapshotData(),
|
||||
}
|
||||
return json.Marshal(msg)
|
||||
}
|
||||
|
||||
// AddSubscriber registers for broadcast fan-out (call after sending the initial snapshot).
|
||||
func (h *Hub) AddSubscriber(c *websocket.Conn) {
|
||||
h.subsMu.Lock()
|
||||
h.conns[c] = struct{}{}
|
||||
h.subsMu.Unlock()
|
||||
}
|
||||
|
||||
func (h *Hub) Unregister(c *websocket.Conn) {
|
||||
h.subsMu.Lock()
|
||||
delete(h.conns, c)
|
||||
h.subsMu.Unlock()
|
||||
}
|
||||
|
||||
// BroadcastSnapshot pushes full snapshot to all subscribers.
|
||||
func (h *Hub) BroadcastSnapshot() {
|
||||
body, err := h.BuildSnapshotMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h.subsMu.Lock()
|
||||
defer h.subsMu.Unlock()
|
||||
for c := range h.conns {
|
||||
_ = c.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
if err := c.WriteMessage(websocket.TextMessage, body); err != nil {
|
||||
_ = c.Close()
|
||||
delete(h.conns, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NotifyMetricsUpdate should be called after SetMetrics to push to browsers.
|
||||
func (h *Hub) NotifyMetricsUpdate(serverID string, metricsJSON []byte) {
|
||||
h.SetMetrics(serverID, metricsJSON)
|
||||
h.BroadcastSnapshot()
|
||||
}
|
||||
82
mengyamonitor-backend-server/internal/ingest/server.go
Normal file
82
mengyamonitor-backend-server/internal/ingest/server.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package ingest
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"mengyamonitor-backend-server/internal/gen/mengyav1"
|
||||
"mengyamonitor-backend-server/internal/hub"
|
||||
"mengyamonitor-backend-server/internal/store"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
mengyav1.UnimplementedAgentIngestServer
|
||||
Store *store.Store
|
||||
Hub *hub.Hub
|
||||
}
|
||||
|
||||
func (s *Server) Connect(stream grpc.BidiStreamingServer[mengyav1.AgentMessage, mengyav1.ControlMessage]) error {
|
||||
var authedID string
|
||||
for {
|
||||
msg, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := stream.Context()
|
||||
|
||||
switch {
|
||||
case msg.GetHello() != nil:
|
||||
h := msg.GetHello()
|
||||
srv, err := s.Store.VerifyAgent(ctx, h.GetServerId(), h.GetAgentKey())
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
log.Printf("ingest: auth failed server_id=%q: no such id (SERVER_ID must be the UUID from admin table, not the display name)", h.GetServerId())
|
||||
} else {
|
||||
log.Printf("ingest: auth failed server_id=%s: %v", h.GetServerId(), err)
|
||||
}
|
||||
return status.Errorf(codes.Unauthenticated, "invalid credentials")
|
||||
}
|
||||
authedID = srv.ID
|
||||
_ = stream.Send(&mengyav1.ControlMessage{
|
||||
Payload: &mengyav1.ControlMessage_Ack{
|
||||
Ack: &mengyav1.Ack{Message: "hello_ok"},
|
||||
},
|
||||
})
|
||||
|
||||
case msg.GetHeartbeat() != nil:
|
||||
if authedID == "" {
|
||||
continue
|
||||
}
|
||||
_ = stream.Send(&mengyav1.ControlMessage{
|
||||
Payload: &mengyav1.ControlMessage_Ack{
|
||||
Ack: &mengyav1.Ack{Message: "pong"},
|
||||
},
|
||||
})
|
||||
|
||||
case msg.GetMetrics() != nil:
|
||||
if authedID == "" {
|
||||
return status.Errorf(codes.FailedPrecondition, "send hello first")
|
||||
}
|
||||
m := msg.GetMetrics()
|
||||
if len(m.GetPayloadJson()) == 0 {
|
||||
continue
|
||||
}
|
||||
s.Hub.NotifyMetricsUpdate(authedID, []byte(m.GetPayloadJson()))
|
||||
_ = stream.Send(&mengyav1.ControlMessage{
|
||||
Payload: &mengyav1.ControlMessage_Ack{
|
||||
Ack: &mengyav1.Ack{Message: "metrics_ok"},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
23
mengyamonitor-backend-server/internal/middleware/admin.go
Normal file
23
mengyamonitor-backend-server/internal/middleware/admin.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"net/http"
|
||||
|
||||
"mengyamonitor-backend-server/internal/config"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminToken rejects requests without a valid X-Admin-Token header.
|
||||
func AdminToken(cfg *config.Config) gin.HandlerFunc {
|
||||
tok := []byte(cfg.AdminToken)
|
||||
return func(c *gin.Context) {
|
||||
given := []byte(c.GetHeader("X-Admin-Token"))
|
||||
if subtle.ConstantTimeCompare(given, tok) != 1 {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
35
mengyamonitor-backend-server/internal/model/server.go
Normal file
35
mengyamonitor-backend-server/internal/model/server.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// MonitoredServer is persisted and exposed to the frontend display.
|
||||
type MonitoredServer struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Enabled bool `json:"enabled"`
|
||||
SortOrder int `json:"sortOrder"`
|
||||
AgentKey string `json:"agentKey,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
}
|
||||
|
||||
type CreateServerInput struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
URL string `json:"url"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type UpdateServerInput struct {
|
||||
Name string `json:"name"`
|
||||
URL *string `json:"url"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
AgentKey *string `json:"agentKey"`
|
||||
}
|
||||
|
||||
type ReorderInput struct {
|
||||
IDs []string `json:"ids" binding:"required"`
|
||||
}
|
||||
|
||||
type AuthInput struct {
|
||||
Token string `json:"token" binding:"required"`
|
||||
}
|
||||
71
mengyamonitor-backend-server/internal/router/router.go
Normal file
71
mengyamonitor-backend-server/internal/router/router.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"mengyamonitor-backend-server/internal/config"
|
||||
"mengyamonitor-backend-server/internal/handler"
|
||||
"mengyamonitor-backend-server/internal/hub"
|
||||
"mengyamonitor-backend-server/internal/middleware"
|
||||
"mengyamonitor-backend-server/internal/store"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func New(cfg *config.Config, st *store.Store, h *hub.Hub) *gin.Engine {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
r := gin.New()
|
||||
r.Use(gin.Logger(), gin.Recovery())
|
||||
|
||||
// 公网宽松 CORS:任意 Origin + 透传浏览器请求的 Header(预检)
|
||||
r.Use(func(c *gin.Context) {
|
||||
h := c.Writer.Header()
|
||||
origin := c.GetHeader("Origin")
|
||||
if origin != "" {
|
||||
h.Set("Access-Control-Allow-Origin", origin)
|
||||
h.Set("Access-Control-Allow-Credentials", "true")
|
||||
} else {
|
||||
h.Set("Access-Control-Allow-Origin", "*")
|
||||
}
|
||||
h.Set("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS")
|
||||
if reqH := c.GetHeader("Access-Control-Request-Headers"); reqH != "" {
|
||||
h.Set("Access-Control-Allow-Headers", reqH)
|
||||
} else {
|
||||
h.Set("Access-Control-Allow-Headers", "Content-Type, X-Admin-Token, Authorization, Accept, Origin, X-Requested-With")
|
||||
}
|
||||
h.Set("Access-Control-Max-Age", "86400")
|
||||
if c.Request.Method == http.MethodOptions {
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
})
|
||||
|
||||
pub := &handler.Public{Store: st}
|
||||
r.GET("/", pub.Root)
|
||||
r.GET("/api/health", pub.Health)
|
||||
r.GET("/api/servers", pub.ListServers)
|
||||
r.GET("/api/ws/monitor", handler.MonitorWebSocket(h))
|
||||
|
||||
adm := &handler.Admin{Store: st, Cfg: cfg}
|
||||
r.POST("/api/admin/auth", adm.Auth)
|
||||
adminG := r.Group("/api/admin")
|
||||
adminG.Use(middleware.AdminToken(cfg))
|
||||
{
|
||||
adminG.GET("/servers", adm.ListServers)
|
||||
adminG.POST("/servers", adm.CreateServer)
|
||||
adminG.PUT("/servers/reorder", adm.Reorder)
|
||||
adminG.PUT("/servers/:id", adm.UpdateServer)
|
||||
adminG.DELETE("/servers/:id", adm.DeleteServer)
|
||||
}
|
||||
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"error": "not_found",
|
||||
"path": c.Request.URL.Path,
|
||||
"method": c.Request.Method,
|
||||
})
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
307
mengyamonitor-backend-server/internal/store/sqlite.go
Normal file
307
mengyamonitor-backend-server/internal/store/sqlite.go
Normal file
@@ -0,0 +1,307 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"mengyamonitor-backend-server/internal/model"
|
||||
|
||||
"github.com/google/uuid"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func Open(dbPath string) (*Store, error) {
|
||||
dir := filepath.Dir(dbPath)
|
||||
if dir != "." && dir != "" {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
db, err := sql.Open("sqlite", dbPath+"?_pragma=foreign_keys(1)")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := db.Ping(); err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
s := &Store{db: db}
|
||||
if err := s.migrate(); err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Store) Close() error {
|
||||
return s.db.Close()
|
||||
}
|
||||
|
||||
func (s *Store) migrate() error {
|
||||
const ddl = `
|
||||
CREATE TABLE IF NOT EXISTS servers (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_servers_sort ON servers(sort_order);
|
||||
`
|
||||
if _, err := s.db.Exec(ddl); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.ensureAgentKeyColumn()
|
||||
}
|
||||
|
||||
func (s *Store) ensureAgentKeyColumn() error {
|
||||
rows, err := s.db.Query(`PRAGMA table_info(servers)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var cid int
|
||||
var name, typ string
|
||||
var notnull, pk int
|
||||
var dflt sql.NullString
|
||||
if err := rows.Scan(&cid, &name, &typ, ¬null, &dflt, &pk); err != nil {
|
||||
return err
|
||||
}
|
||||
if name == "agent_key" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = s.db.Exec(`ALTER TABLE servers ADD COLUMN agent_key TEXT`); err != nil {
|
||||
return err
|
||||
}
|
||||
// Backfill keys for existing rows so gRPC can be enabled without manual SQL.
|
||||
bRows, err := s.db.Query(`SELECT id FROM servers WHERE agent_key IS NULL OR agent_key = ''`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ids []string
|
||||
for bRows.Next() {
|
||||
var id string
|
||||
if err := bRows.Scan(&id); err != nil {
|
||||
_ = bRows.Close()
|
||||
return err
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
_ = bRows.Close()
|
||||
for _, id := range ids {
|
||||
if _, err := s.db.Exec(`UPDATE servers SET agent_key = ? WHERE id = ?`, uuid.NewString(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) ListEnabled(ctx context.Context) ([]model.MonitoredServer, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `
|
||||
SELECT id, name, url, enabled, sort_order, agent_key, created_at
|
||||
FROM servers WHERE enabled = 1 ORDER BY sort_order ASC, created_at ASC`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanServers(rows)
|
||||
}
|
||||
|
||||
func (s *Store) ListAll(ctx context.Context) ([]model.MonitoredServer, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `
|
||||
SELECT id, name, url, enabled, sort_order, agent_key, created_at
|
||||
FROM servers ORDER BY sort_order ASC, created_at ASC`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanServers(rows)
|
||||
}
|
||||
|
||||
func scanServers(rows *sql.Rows) ([]model.MonitoredServer, error) {
|
||||
out := make([]model.MonitoredServer, 0)
|
||||
for rows.Next() {
|
||||
var m model.MonitoredServer
|
||||
var enabled int
|
||||
var created, agKey sql.NullString
|
||||
if err := rows.Scan(&m.ID, &m.Name, &m.URL, &enabled, &m.SortOrder, &agKey, &created); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.Enabled = enabled != 0
|
||||
if agKey.Valid {
|
||||
m.AgentKey = agKey.String
|
||||
}
|
||||
if t, err := time.Parse(time.RFC3339Nano, created.String); err == nil {
|
||||
m.CreatedAt = t
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) Create(ctx context.Context, in model.CreateServerInput) (*model.MonitoredServer, error) {
|
||||
name := strings.TrimSpace(in.Name)
|
||||
urlStr := normalizeURL(strings.TrimSpace(in.URL))
|
||||
if name == "" {
|
||||
return nil, errors.New("invalid name")
|
||||
}
|
||||
enabled := true
|
||||
if in.Enabled != nil {
|
||||
enabled = *in.Enabled
|
||||
}
|
||||
var maxOrder int
|
||||
_ = s.db.QueryRowContext(ctx, `SELECT COALESCE(MAX(sort_order), -1) FROM servers`).Scan(&maxOrder)
|
||||
id := uuid.NewString()
|
||||
agentKey := uuid.NewString()
|
||||
now := time.Now().UTC()
|
||||
_, err := s.db.ExecContext(ctx, `
|
||||
INSERT INTO servers (id, name, url, enabled, sort_order, agent_key, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
id, name, urlStr, boolToInt(enabled), maxOrder+1, agentKey, now.Format(time.RFC3339Nano))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.MonitoredServer{
|
||||
ID: id, Name: name, URL: urlStr, Enabled: enabled, SortOrder: maxOrder + 1, AgentKey: agentKey, CreatedAt: now,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) Update(ctx context.Context, id string, in model.UpdateServerInput) (*model.MonitoredServer, error) {
|
||||
cur, err := s.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := cur.Name
|
||||
urlStr := cur.URL
|
||||
enabled := cur.Enabled
|
||||
if in.Name != "" {
|
||||
name = strings.TrimSpace(in.Name)
|
||||
}
|
||||
if in.URL != nil {
|
||||
urlStr = normalizeURL(strings.TrimSpace(*in.URL))
|
||||
}
|
||||
if in.Enabled != nil {
|
||||
enabled = *in.Enabled
|
||||
}
|
||||
if name == "" {
|
||||
return nil, errors.New("invalid name")
|
||||
}
|
||||
agentKey := cur.AgentKey
|
||||
if in.AgentKey != nil {
|
||||
agentKey = *in.AgentKey
|
||||
}
|
||||
_, err = s.db.ExecContext(ctx, `UPDATE servers SET name = ?, url = ?, enabled = ?, agent_key = ? WHERE id = ?`,
|
||||
name, urlStr, boolToInt(enabled), nullIfEmpty(agentKey), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cur.Name, cur.URL, cur.Enabled, cur.AgentKey = name, urlStr, enabled, agentKey
|
||||
return cur, nil
|
||||
}
|
||||
|
||||
func (s *Store) Get(ctx context.Context, id string) (*model.MonitoredServer, error) {
|
||||
var m model.MonitoredServer
|
||||
var enabled int
|
||||
var created, agKey sql.NullString
|
||||
err := s.db.QueryRowContext(ctx, `
|
||||
SELECT id, name, url, enabled, sort_order, agent_key, created_at FROM servers WHERE id = ?`, id).
|
||||
Scan(&m.ID, &m.Name, &m.URL, &enabled, &m.SortOrder, &agKey, &created)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.Enabled = enabled != 0
|
||||
if agKey.Valid {
|
||||
m.AgentKey = agKey.String
|
||||
}
|
||||
if t, err := time.Parse(time.RFC3339Nano, created.String); err == nil {
|
||||
m.CreatedAt = t
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// VerifyAgent checks server_id + agent_key for gRPC ingest.
|
||||
func (s *Store) VerifyAgent(ctx context.Context, id, key string) (*model.MonitoredServer, error) {
|
||||
srv, err := s.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !srv.Enabled {
|
||||
return nil, errors.New("disabled")
|
||||
}
|
||||
if srv.AgentKey == "" {
|
||||
return nil, errors.New("agent_key not set")
|
||||
}
|
||||
if subtle.ConstantTimeCompare([]byte(srv.AgentKey), []byte(key)) != 1 {
|
||||
return nil, errors.New("invalid agent_key")
|
||||
}
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
func nullIfEmpty(s string) any {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Store) Delete(ctx context.Context, id string) error {
|
||||
res, err := s.db.ExecContext(ctx, `DELETE FROM servers WHERE id = ?`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, _ := res.RowsAffected()
|
||||
if n == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) Reorder(ctx context.Context, ids []string) error {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
for i, id := range ids {
|
||||
if _, err := tx.ExecContext(ctx, `UPDATE servers SET sort_order = ? WHERE id = ?`, i, id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func boolToInt(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func normalizeURL(u string) string {
|
||||
if u == "" {
|
||||
return ""
|
||||
}
|
||||
if !strings.HasPrefix(strings.ToLower(u), "http://") && !strings.HasPrefix(strings.ToLower(u), "https://") {
|
||||
return "http://" + u
|
||||
}
|
||||
return u
|
||||
}
|
||||
39
mengyamonitor-backend-server/main.go
Normal file
39
mengyamonitor-backend-server/main.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"mengyamonitor-backend-server/internal/config"
|
||||
"mengyamonitor-backend-server/internal/grpcrun"
|
||||
"mengyamonitor-backend-server/internal/hub"
|
||||
"mengyamonitor-backend-server/internal/ingest"
|
||||
"mengyamonitor-backend-server/internal/router"
|
||||
"mengyamonitor-backend-server/internal/store"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := config.Load()
|
||||
st, err := store.Open(cfg.DBPath)
|
||||
if err != nil {
|
||||
log.Fatalf("store: %v", err)
|
||||
}
|
||||
defer st.Close()
|
||||
|
||||
metricsHub := hub.New()
|
||||
ingestSrv := &ingest.Server{Store: st, Hub: metricsHub}
|
||||
|
||||
grpcAddr := fmt.Sprintf("%s:%s", cfg.Host, cfg.GRPCPort)
|
||||
go func() {
|
||||
log.Printf("gRPC AgentIngest listening on %s", grpcAddr)
|
||||
if err := grpcrun.Serve(grpcAddr, ingestSrv); err != nil {
|
||||
log.Fatalf("grpc: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
httpAddr := fmt.Sprintf("%s:%s", cfg.Host, cfg.Port)
|
||||
log.Printf("central HTTP/WS on http://%s", httpAddr)
|
||||
if err := router.New(cfg, st, metricsHub).Run(httpAddr); err != nil {
|
||||
log.Fatalf("http: %v", err)
|
||||
}
|
||||
}
|
||||
41
mengyamonitor-backend-server/proto/mengya/v1/agent.proto
Normal file
41
mengyamonitor-backend-server/proto/mengya/v1/agent.proto
Normal file
@@ -0,0 +1,41 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package mengya.v1;
|
||||
|
||||
option go_package = "mengyamonitor-backend-server/internal/gen/mengyav1;mengyav1";
|
||||
|
||||
// AgentIngest: 采集端出站连接,双向流上报指标。
|
||||
service AgentIngest {
|
||||
rpc Connect(stream AgentMessage) returns (stream ControlMessage);
|
||||
}
|
||||
|
||||
message AgentMessage {
|
||||
oneof payload {
|
||||
Hello hello = 1;
|
||||
Heartbeat heartbeat = 2;
|
||||
MetricsReport metrics = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message Hello {
|
||||
string server_id = 1;
|
||||
string agent_key = 2;
|
||||
string hostname = 3;
|
||||
}
|
||||
|
||||
message Heartbeat {}
|
||||
|
||||
message MetricsReport {
|
||||
string payload_json = 1;
|
||||
int64 collected_at_unix_ms = 2;
|
||||
}
|
||||
|
||||
message ControlMessage {
|
||||
oneof payload {
|
||||
Ack ack = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message Ack {
|
||||
string message = 1;
|
||||
}
|
||||
Reference in New Issue
Block a user