diff --git a/.github/workflows/buildapp-dev.yml b/.github/workflows/buildapp-dev.yml index 6a783a9..7bba730 100644 --- a/.github/workflows/buildapp-dev.yml +++ b/.github/workflows/buildapp-dev.yml @@ -7,38 +7,17 @@ on: workflow_dispatch: push: branches: - - dev + - main jobs: - build_win: - runs-on: ubuntu-20.04 + build: + runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.21' - - - name: Install UPX - run: sudo apt-get install upx - - - name: download modules - run: go mod download - - - name: Run build script - run: bash build.sh dev win - - build_linux: - runs-on: ubuntu-20.04 - steps: - - - name: Checkout code - uses: actions/checkout@v3 - + - name: Set up Go uses: actions/setup-go@v4 with: @@ -46,36 +25,35 @@ jobs: - name: Install UPX run: sudo apt-get install upx - - - name: download modules - run: go mod download - - - name: Run build script - run: bash build.sh dev linux - - build_darwin: - runs-on: ubuntu-20.04 - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.21' - - - name: Install UPX - run: sudo apt-get install upx - - name: download modules run: go mod download - name: Run build script - run: bash build.sh dev darwin - + # 传入commit sha + run: bash build.sh ${{ github.sha }} + + # 安装 rclone + # dev版本不另外发release + - name: Install rclone + run: | + cd ~ + curl https://rclone.org/install.sh | sudo bash + # 配置 rclone + - name: Configure rclone + run: | + mkdir -p ~/.config/rclone + cat > ~/.config/rclone/rclone.conf << EOF + ${{ secrets.RCLONECONFIG }} + EOF + - name: Sync to OneDrive + run: | + sudo timedatectl set-timezone "Asia/Shanghai" + rclone mkdir one:/share/Mirouter-ui/dev/${{ github.sha }} + rclone sync ./build one:/share/Mirouter-ui/dev/${{ github.sha }} builddocker: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Checkout @@ -99,4 +77,6 @@ jobs: context: . platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 push: true - tags: thun888/mirouter-ui:dev \ No newline at end of file + tags: thun888/mirouter-ui:dev + build-args: | + VERSION=${{ github.sha }} diff --git a/.github/workflows/buildapp.yml b/.github/workflows/buildapp.yml index d0dfff0..ec629e5 100644 --- a/.github/workflows/buildapp.yml +++ b/.github/workflows/buildapp.yml @@ -13,7 +13,7 @@ on: jobs: build_win: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Checkout code @@ -34,13 +34,13 @@ jobs: run: bash build.sh ${{ github.event.inputs.version }} win - name: upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: mirouter-ui-win path: ./build/** build_linux: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Checkout code @@ -61,13 +61,13 @@ jobs: run: bash build.sh ${{ github.event.inputs.version }} linux - name: upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: mirouter-ui-linux path: ./build/** build_darwin: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 @@ -87,13 +87,13 @@ jobs: run: bash build.sh ${{ github.event.inputs.version }} darwin - name: upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: mirouter-ui-darwin path: ./build/** post_release: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: [build_win, build_linux, build_darwin] steps: - name: Download artifacts @@ -112,53 +112,52 @@ jobs: name: mirouter-ui-darwin path: ./build - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.TOKEN }} + - name: Upload Release Assets + uses: ncipollo/release-action@v1 with: - tag_name: ${{ github.event.inputs.version }} - release_name: Release ${{ github.event.inputs.version }} + artifacts: "build/*" + token: ${{ secrets.TOKEN }} + tag: ${{ github.event.inputs.version }} + name: Release ${{ github.event.inputs.version }} draft: true - prerelease: false - - name: Upload Release Assets + # 安装 rclone + - name: Install rclone + run: | + cd ~ + curl https://rclone.org/install.sh | sudo bash + # 配置 rclone + - name: Configure rclone + run: | + mkdir -p ~/.config/rclone + cat > ~/.config/rclone/rclone.conf << EOF + ${{ secrets.RCLONECONFIG }} + EOF + - name: Sync to OneDrive run: | - for file in ./build/*; do - if [ -f "$file" ]; then - echo "Uploading $file" - curl \ - -H "Authorization: token ${{ secrets.TOKEN }}" \ - -H "Content-Type: $(file -b --mime-type $file)" \ - --data-binary @"$file" \ - "${{ steps.create_release.outputs.upload_url }}=$(basename $file)" - fi - done - + sudo timedatectl set-timezone "Asia/Shanghai" + rclone mkdir one:/share/Mirouter-ui/${{ github.event.inputs.version }} + rclone sync ./build one:/share/Mirouter-ui/${{ github.event.inputs.version }} builddocker: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - - name: Checkout + - name: Checkout uses: actions/checkout@v3 - - - name: Set up QEMU + - name: Set up QEMU uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - - - name: Login to Docker Hub + - name: Login to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - - - name: Build and push + - name: Build and push uses: docker/build-push-action@v4 with: context: . platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 push: true tags: thun888/mirouter-ui:latest + build-args: | + VERSION=${{ github.event.inputs.version }} diff --git a/.github/workflows/buildappwhr.yml b/.github/workflows/buildappwhr.yml new file mode 100644 index 0000000..e904d54 --- /dev/null +++ b/.github/workflows/buildappwhr.yml @@ -0,0 +1,170 @@ +name: Build and Release without history record +run-name: Build ${{ github.event.inputs.version }} without history record by @${{ github.actor }} + +permissions: write-all + +on: + workflow_dispatch: + inputs: + version: + description: '版本号' + required: true + + +jobs: + build_win: + runs-on: ubuntu-latest + steps: + + - name: Checkout code + uses: actions/checkout@v3 + with: + ref: whr + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Install UPX + run: sudo apt-get install upx + + - name: download modules + run: go mod download + + - name: Run build script + run: bash build.sh ${{ github.event.inputs.version }} win + + - name: upload artifact + uses: actions/upload-artifact@v4 + with: + name: mirouter-ui-win + path: ./build/** + + build_linux: + runs-on: ubuntu-latest + steps: + + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Install UPX + run: sudo apt-get install upx + + - name: download modules + run: go mod download + + - name: Run build script + run: bash build.sh ${{ github.event.inputs.version }} linux + + - name: upload artifact + uses: actions/upload-artifact@v4 + with: + name: mirouter-ui-linux + path: ./build/** + + build_darwin: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Install UPX + run: sudo apt-get install upx + + - name: download modules + run: go mod download + + - name: Run build script + run: bash build.sh ${{ github.event.inputs.version }} darwin + + - name: upload artifact + uses: actions/upload-artifact@v4 + with: + name: mirouter-ui-darwin + path: ./build/** + + post_release: + runs-on: ubuntu-latest + needs: [build_win, build_linux, build_darwin] + steps: + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: mirouter-ui-win + path: ./build + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: mirouter-ui-linux + path: ./build + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: mirouter-ui-darwin + path: ./build + + - name: Upload Release Assets + uses: ncipollo/release-action@v1 + with: + artifacts: "build/*" + token: ${{ secrets.TOKEN }} + tag: ${{ github.event.inputs.version }}whr + name: Release ${{ github.event.inputs.version }} without history record + draft: true + + # 安装 rclone + - name: Install rclone + run: | + cd ~ + curl https://rclone.org/install.sh | sudo bash + + # 配置 rclone + - name: Configure rclone + run: | + mkdir -p ~/.config/rclone + cat > ~/.config/rclone/rclone.conf << EOF + ${{ secrets.RCLONECONFIG }} + EOF + - name: Sync to OneDrive + run: | + sudo timedatectl set-timezone "Asia/Shanghai" + rclone mkdir one:/share/Mirouter-ui/whr/${{ github.event.inputs.version }} + rclone sync ./build one:/share/Mirouter-ui/whr/${{ github.event.inputs.version }} + + builddocker: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - + name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + - + name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 + push: true + tags: thun888/mirouter-ui:whr diff --git a/.gitignore b/.gitignore index ff6a6bc..33b4154 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ build/ dist/ main.exe +aa.sh +static/* +mirouter-ui.exe +database.db diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index a490b61..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "githubPullRequests.ignoredPullRequestBranches": [ - "main" - ] -} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 93585f2..ce6e6f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,16 @@ +ARG VERSION + FROM golang:1.21.0-alpine3.18 AS builder WORKDIR /app COPY . . +RUN echo "Building version: $VERSION" + RUN go mod download -RUN go build -ldflags "-X 'main.Version=Docker'" -o main . +RUN go build -ldflags "-X 'main.Version=$VERSION'" -o main . FROM alpine:3.18 @@ -16,4 +20,4 @@ COPY --from=builder /app/main /app/main EXPOSE 6789 -CMD ["./main","--config=/app/data/config.json","--basedirectory=/app/data/","--databasepath=/app/data/database.db"] +CMD ["./main","--config=/app/data/config.yaml","--workdirectory=/app/data/","--databasepath=/app/data/database.db","--autocheckupdate=false"] \ No newline at end of file diff --git a/README.md b/README.md index 5f2de30..4c0acf1 100644 --- a/README.md +++ b/README.md @@ -1,167 +1,20 @@ -![mrui-logo](https://github.com/Mirouterui/mirouter-ui/assets/63234268/da737f28-e8b6-42d7-a21e-70be2d53fb78) +![mrui-logo](./otherfile/images/logo.png) ## Mirouter-ui > 😎 基于小米路由器API的展示面板 [![Docker Pulls](https://img.shields.io/docker/pulls/thun888/mirouter-ui)](https://hub.docker.com/r/thun888/mirouter-ui) +[![HitCount](https://hits.dwyl.com/Mirouterui/mirouter-ui.svg?style=flat)](http://hits.dwyl.com/Mirouterui/mirouter-ui) [![Release And Docker](https://github.com/Mirouterui/mirouter-ui/actions/workflows/buildapp.yml/badge.svg)](https://github.com/Mirouterui/mirouter-ui/actions/workflows/buildapp.yml) [![Build DEV version](https://github.com/Mirouterui/mirouter-ui/actions/workflows/buildapp-dev.yml/badge.svg)](https://github.com/Mirouterui/mirouter-ui/actions/workflows/buildapp-dev.yml) -将本程序部署在小米路由器的网络环境中,配置完成即可食用 -后端基于`Golang`,多平台兼容 -已在小米路由器R1D,R4A上测试通过 +部署请移步至[官方网站](https://mrui.hzchu.top/) -部分新路由无法获取cpu占用,如红米AX6000,AX1800。可在路由器上运行解决。 -## 图片展示 -#### 首页 - -![index](https://github.com/thun888/mirouter-ui/assets/63234268/48bbf554-ec03-41dc-b5fd-42b5faeba466) - -#### 设备列表 - -![Snipaste_2023-08-24_14-53-25](https://github.com/Mirouterui/mirouter-ui/assets/63234268/47309e3a-cc02-479c-a9d3-29cfca235a83) - - -#### 设备详情 - -![device_index](https://github.com/thun888/mirouter-ui/assets/63234268/20c465e1-660b-41bf-a200-973423057d31) - -#### 路由器详情 - -![router_index](https://github.com/thun888/mirouter-ui/assets/63234268/1ddce346-7abd-4816-bc55-fe55d3dc70c9) - -#### 温度显示(仅支持部分设备) - -![Snipaste_2023-08-25_13-33-54](https://github.com/Mirouterui/mirouter-ui/assets/63234268/0926dafd-a63e-4ee6-bc61-f381c1dfc199) - -#### 历史数据统计 - -![history_index](./otherfile/images/history_index.png) -## 部署 - -### Docker - -> docker run -d -p 6789:6789 -v $(pwd):/app/data --name mirouter-ui --restart=always thun888/mirouter-ui - -新建一个文件夹,并在该文件夹里运行上述命令,程序会在该文件夹里生成配置文件,修改即可 - -### 直接运行 - -#### 下载 - -从[Release](https://github.com/thun888/mirouter-ui/releases/)下载二进制文件 - -> 可访问[镜像站](https://mrui-api.hzchu.top/down/)以获取更快的速度 - -如果路由器有足够(内存)空间可以下载对应架构版本的部署在路由器上(ps:使用`uname -m`查看,若为armv7l,请使用armv5版本) - -![image](https://github.com/Mirouterui/mirouter-ui/assets/63234268/5dfa3deb-0aab-4198-9170-5af1141b3746) - - - -#### 获取key - -> 自动获取:[Mirouterui/MiKVIVator](https://github.com/Mirouterui/MiKVIVator) -> ps:我在3个路由器上发现了一样的数值,已添加为默认值,如果无法登录再尝试更改吧 - -打开路由器登录页面,右键,点击`查看页面源代码`,按下`CTRL + F`组合键打开搜索框,搜索`key:`,不出意外你能看见以下结果 - -![image](https://github.com/thun888/mirouter-ui/assets/63234268/87dd59bd-dc9f-4a9f-b22f-d5fd9a9d047a) - -复制双引号里的内容粘贴到`config.json`对应栏目中,并填上密码(路由器后台密码) - -![image](./otherfile/images/config.png) - - -> config.json 会在初次运行时自动下载 -> ip可以根据实际情况修改 - -**配置项**: - -| 配置名 | 默认值 | 解释 | -| ------ | ------ | ------------------------------------------------------------ | -| dev | [] | 路由器信息,参阅`dev项` | -| history | [] | 历史记录相关功能,参阅`history项` | -| tiny | false | 启用后,不再下载静态文件,需搭配[在线前端](http://mrui.hzchu.top:8880/)使用 | -| netdata_routerid | 0 | 调用netdata api时返回的路由器(对应dev项中第n个) | -| flushTokenTime | 1800 | 刷新token时间间隔(s) | -| port | 6789 | 网页页面端口号 | -| debug | true | debug模式,建议在测试正常后关闭 | - -**dev**项: - -| 配置名 | 默认值 | 解释 | -| ---------- | -------------------------------- | --------------------------------------- | -| password | | 路由器管理后台密码 | -| key | a2ffa5c9be07488bbb04a3a47d3c5f6a | 路由器管理后台key | -| ip | 192.168.31.1 | 路由器IP | -| routerunit | false | 启用后,程序通过`gopsutil`库获取CPU占用 | - -**history**项: - -| 配置名 | 默认值 | 解释 | -| ---------- | -------------------------------- | --------------------------------------- | -| enable | false | 是否启用历史数据统计 | -| sampletime | 300 | 采样时间间隔(s) | -| maxsaved | 8640 | 最多记录条数 | - -命令行参数: - -| 参数 | 解释 | -| --------------- | -------------------------------- | -| --config | 配置文件路径,默认为“./config.json” | -| --basedirectory | 基础目录路径,在里面存放静态文件 | -| --databasepath | 数据库路径,默认为“./database.db” | - - -然后运行即可 - -此时命令窗口中会显示网页的访问端口,使用设备的`ip地址+端口号(6789)`访问面板 - -### 后台运行 - -Systemd - -```bash -sudo vim /etc/systemd/system/artalk.service -``` - - - -```ini -[Unit] -Description=mrui -After=network.target network-online.target -Requires=network-online.target - -[Service] -ExecStart=/pathto/mrui - -[Install] -WantedBy=multi-user.target -``` - -[windows守护进程工具--nssm详解 - 与f - 博客园 (cnblogs.com)](https://www.cnblogs.com/fps2tao/p/16433588.html) - -### Todo - -- [x] 历史数据统计 -- [x] 深色模式 -- [x] 多路由支持 -- [x] 快捷更新 -- [ ] 设备小工具 -- [x] netdata,api形式兼容 - -[MRUI开发规划](https://bbs.hzchu.top/d/2-mruikai-fa-gui-hua) - -> 主要功能已完成开发,接下来随缘更新😶‍🌫️ - -## Stars~ - -[![Stars~](https://starchart.cc/mirouterui/mirouter-ui.svg)](https://starchart.cc/mirouterui/mirouter-ui) +## 状态 +![Repobeats analytics](https://repobeats.axiom.co/api/embed/5c772eb2070995571e015079682c17dd72a74e2f.svg "Repobeats analytics image") diff --git a/changelog.md b/changelog.md deleted file mode 100644 index ff40256..0000000 --- a/changelog.md +++ /dev/null @@ -1,41 +0,0 @@ -### 2023/12/03 -`M` 优化静态资源下载逻辑 -### 2023/11/17 -`A` 增加upnp显示 -`M` 优化 -### 2023/10/06 -`A`支持历史数据统计 - -**注意**:由于libc的问题,在以下架构上不适用 - win_i386 - linux_mips - linux_mipsle - - -可下载v1.1.5版本,手动从[这里](https://raw.githubusercontent.com/Mirouterui/mirouter-ui/9526bb3f59188029544e0e88201969eb9677a087/config.json)下载配置文件并修改 - -### 2023/10/05 - -`A`支持历史数据统计 - -**注意**:由于libc的问题,在以下架构上不适用 - win_i386 - linux_mips - linux_mipsle - - -可下载v1.1.5版本,手动从[这里](https://raw.githubusercontent.com/Mirouterui/mirouter-ui/9526bb3f59188029544e0e88201969eb9677a087/config.json)下载配置文件并修改 - -### 2023/10/02 - -`A`完善多IP处理 - -`A`"检查更新"页面 - -`A`支持深色模式 - -`M`修改折线图展示模式,不再累计数据,仅保留近5分钟数据 - -`M`图表自适应 - -`M`页面小改动&优化 \ No newline at end of file diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..3cd4569 --- /dev/null +++ b/config.yaml @@ -0,0 +1,23 @@ +# 路由器设备配置 +dev: + - password: "" # 路由器密码 + key: "a2ffa5c9be07488bbb04a3a47d3c5f6a" # 路由器密钥 + ip: "192.168.31.1" # 路由器IP地址 + routerunit: false # 是否为路由器单元 + islocal: false # 是否为本地模式 + +# 历史记录配置 +history: + enable: true # 是否启用历史记录 + maxsaved: 3000 # 最大保存记录数 + sampletime: 300 # 采样时间(秒) + +# 应用配置 +debug: true # 调试模式 +port: 6789 # 服务端口 +address: "0.0.0.0" # 监听地址 +tiny: false # 精简模式 +flushTokenTime: 1800 # Token刷新时间(秒) +netdata_routernum: 0 # netdata路由器ID +safemode: true # 安全模式 +api_key: "" # 认证令牌 \ No newline at end of file diff --git a/go.mod b/go.mod index 8254921..7f8b2ca 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,59 @@ -module main +module github.com/Mirouterui/mirouter-ui -go 1.19 +go 1.24.3 require ( - github.com/labstack/echo/v4 v4.11.1 - github.com/shirou/gopsutil v3.21.11+incompatible + github.com/gin-gonic/gin v1.9.1 github.com/sirupsen/logrus v1.9.3 - gorm.io/gorm v1.25.2 ) require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/go-ole/go-ole v1.2.6 // 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.14.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // 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/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/protobuf v1.36.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.22.5 // indirect modernc.org/mathutil v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect @@ -23,22 +61,13 @@ require ( ) require ( - github.com/glebarez/sqlite v1.9.0 - github.com/go-ole/go-ole v1.2.6 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect - github.com/labstack/gommon v0.4.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/glebarez/sqlite v1.11.0 + github.com/mitchellh/mapstructure v1.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/robfig/cron/v3 v3.0.1 - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/yusufpapurcu/wmi v1.2.3 // indirect - golang.org/x/crypto v0.11.0 // indirect - golang.org/x/net v0.12.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.11.0 // indirect - golang.org/x/time v0.3.0 // indirect + github.com/shirou/gopsutil v2.21.11+incompatible + github.com/shirou/gopsutil/v3 v3.24.5 + github.com/spf13/viper v1.20.1 + golang.org/x/sys v0.33.0 // indirect + gorm.io/gorm v1.30.0 ) diff --git a/go.sum b/go.sum index 2f6a8fa..9ae8b2c 100644 --- a/go.sum +++ b/go.sum @@ -1,85 +1,165 @@ +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +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.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= -github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs= -github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +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.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +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/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= -github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 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/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= -github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/shirou/gopsutil v2.21.11+incompatible h1:lOGOyCG67a5dv2hq5Z1BLDUqqKp3HkbjPcz5j6XMS0U= +github.com/shirou/gopsutil v2.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= 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/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.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= -github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= +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.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= -gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= @@ -88,3 +168,4 @@ modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/main.go b/main.go index 4fe4d4d..ab4ea21 100644 --- a/main.go +++ b/main.go @@ -5,13 +5,15 @@ import ( _ "flag" "fmt" "io" - "main/modules/config" - "main/modules/database" - "main/modules/download" - login "main/modules/login" - "main/modules/netdata" - "main/modules/tp" "net/http" + + "github.com/Mirouterui/mirouter-ui/modules/config" + "github.com/Mirouterui/mirouter-ui/modules/database" + "github.com/Mirouterui/mirouter-ui/modules/download" + login "github.com/Mirouterui/mirouter-ui/modules/login" + "github.com/Mirouterui/mirouter-ui/modules/tp" + + // _ "net/http/pprof" "os" "os/exec" "os/signal" @@ -20,8 +22,7 @@ import ( "syscall" "time" - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" + "github.com/gin-gonic/gin" "github.com/robfig/cron/v3" "github.com/shirou/gopsutil/cpu" "github.com/sirupsen/logrus" @@ -38,7 +39,7 @@ var ( routerNames map[int]string hardware string hardwares map[int]string - routerunits map[int]bool + isLocals map[int]bool tiny bool routerunit bool dev []config.Dev @@ -46,7 +47,7 @@ var ( w24g_cmd *exec.Cmd w5g_cmd *exec.Cmd configPath string - basedirectory string + workdirectory string Version string databasepath string flushTokenTime int @@ -54,6 +55,9 @@ var ( historyEnable bool sampletime int netdata_routernum int + safemode bool + api_key string + address string ) type Config struct { @@ -62,25 +66,48 @@ type Config struct { Port int `json:"port"` Tiny bool `json:"tiny"` Databasepath string `json:"databasepath"` + Address string `json:"address"` } func init() { - dev, debug, port, tiny, basedirectory, flushTokenTime, databasepath, maxsaved, historyEnable, sampletime, netdata_routernum = config.GetConfigInfo() + // 加载配置 + cfg, err := config.LoadConfig() + if err != nil { + logrus.Fatal(err) + } + + dev = cfg.Dev + debug = cfg.Debug + port = cfg.Port + tiny = cfg.Tiny + maxsaved = cfg.History.MaxDeleted + historyEnable = cfg.History.Enable + sampletime = cfg.History.Sampletime + flushTokenTime = cfg.FlushTokenTime + netdata_routernum = cfg.Netdata_routernum + workdirectory = cfg.Workdirectory + databasepath = cfg.Databasepath + safemode = cfg.SafeMode + api_key = cfg.ApiKey tokens = make(map[int]string) routerNames = make(map[int]string) hardwares = make(map[int]string) - routerunits = make(map[int]bool) + isLocals = make(map[int]bool) + address = cfg.Address + // go func() { + // logrus.Println(http.ListenAndServe(":6060", nil)) + // }() } func GetCpuPercent() float64 { percent, _ := cpu.Percent(time.Second, false) return percent[0] / 100 } -func getconfig(c echo.Context) error { +func getconfig(c *gin.Context) { type DevNoPassword struct { - Key string `json:"key"` - IP string `json:"ip"` - RouterUnit bool `json:"routerunit"` + Key string `json:"key"` + IP string `json:"ip"` + IsLocal bool `json:"is_local"` } type History struct { Enable bool `json:"enable"` @@ -91,9 +118,9 @@ func getconfig(c echo.Context) error { devsNoPassword := []DevNoPassword{} for _, d := range dev { devNoPassword := DevNoPassword{ - Key: d.Key, - IP: d.IP, - RouterUnit: d.RouterUnit, + Key: d.Key, + IP: d.IP, + IsLocal: d.IsLocal, } devsNoPassword = append(devsNoPassword, devNoPassword) } @@ -102,12 +129,10 @@ func getconfig(c echo.Context) error { history.MaxDeleted = maxsaved history.Databasepath = databasepath history.Sampletime = sampletime - return c.JSON(http.StatusOK, map[string]interface{}{ - "code": 0, - "tiny": tiny, - "port": port, - "debug": debug, - // "token": token, + c.JSON(http.StatusOK, map[string]interface{}{ + "tiny": tiny, + "port": port, + "debug": debug, "dev": devsNoPassword, "history": history, "flushTokenTime": flushTokenTime, @@ -121,344 +146,273 @@ func gettoken(dev []config.Dev) { tokens[i] = token routerNames[i] = routerName hardwares[i] = hardware - routerunits[i] = d.RouterUnit + isLocals[i] = d.IsLocal logrus.Debug(hardwares[i]) } } + +func handleRouterAPI(routernum int, apipath string) (map[string]interface{}, error) { + ip := dev[routernum].IP + url := fmt.Sprintf("http://%s/cgi-bin/luci/;stok=%s/api/%s", ip, tokens[routernum], apipath) + resp, err := http.Get(url) + if err != nil { + return nil, fmt.Errorf("xiaomi router API call failed, please check configuration or router status") + } + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + var result map[string]interface{} + json.Unmarshal(body, &result) + + if isLocals[routernum] && apipath == "/misystem/status" { + cpuPercent := GetCpuPercent() + if cpu, ok := result["cpu"].(map[string]interface{}); ok { + cpu["load"] = cpuPercent + } + } + return result, nil +} + func main() { - starttime := int(time.Now().Unix()) - logrus.Info("当前后端版本为:" + Version) - e := echo.New() - c := cron.New() - e.Use(middleware.Recover()) - // 输出访问日志 - if debug { - e.Use(middleware.Logger()) + // starttime := int(time.Now().Unix()) + logrus.Info("Current backend version: " + Version) + + if !debug { + gin.SetMode(gin.ReleaseMode) } - // e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { - // return func(c echo.Context) error { - // c.Response().Header().Set("Access-Control-Allow-Private-Network", "true") - // return next(c) - // } - // }) - e.Use(middleware.CORS()) + r := gin.New() + + c := cron.New() + + // 添加 CORS 中间件 + r.Use(func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + c.Next() + }) - e.GET("/:routernum/api/:apipath", func(c echo.Context) error { + if !tiny { + directory := "static" + if workdirectory != "" { + directory = filepath.Join(workdirectory, "static") + } + logrus.Debug("Static resource directory: " + directory) + r.Static("/web/", directory) + // 重定向到/web/ + r.GET("/", func(c *gin.Context) { + c.Redirect(http.StatusMovedPermanently, "/web/") + }) + } + + r.GET("/routerapi/:routernum/api/*apipath", func(c *gin.Context) { routernum, err := strconv.Atoi(c.Param("routernum")) if err != nil { - return c.JSON(http.StatusOK, map[string]interface{}{"code": 1100, "msg": "参数错误"}) + c.JSON(http.StatusBadRequest, gin.H{"msg": "Parameter error"}) + return } apipath := c.Param("apipath") - ip := dev[routernum].IP + logrus.Debug(apipath) switch apipath { - - case "xqsystem/router_name": - return c.JSON(http.StatusOK, map[string]interface{}{"code": 0, "routerName": routerNames[routernum]}) - - case "misystem/status", "misystem/devicelist", "xqsystem/internet_connect", "xqsystem/fac_info", "misystem/messages", "xqsystem/upnp": - url := fmt.Sprintf("http://%s/cgi-bin/luci/;stok=%s/api/%s", ip, tokens[routernum], apipath) - resp, err := http.Get(url) + case "/xqsystem/router_name": + c.JSON(http.StatusOK, gin.H{"routerName": routerNames[routernum]}) + return + + case + "/misystem/status", + "/misystem/devicelist", + "/xqsystem/internet_connect", + "/xqsystem/fac_info", + "/misystem/messages", + "/xqsystem/upnp", + "/xqnetwork/diagdevicelist", + "/xqsystem/get_location": + result, err := handleRouterAPI(routernum, apipath) if err != nil { - return c.JSON(http.StatusOK, map[string]interface{}{ - "code": 1101, - "msg": "小米路由器的api调用出错,请检查配置或路由器状态", - }) - } - defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) - var result map[string]interface{} - json.Unmarshal(body, &result) - - if routerunits[routernum] && apipath == "misystem/status" { - cpuPercent := GetCpuPercent() - if cpu, ok := result["cpu"].(map[string]interface{}); ok { - cpu["load"] = cpuPercent - } + c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()}) + return } - return c.JSON(http.StatusOK, result) + c.JSON(http.StatusOK, result) + return default: - return c.JSON(http.StatusOK, map[string]interface{}{ - "code": 1102, - "msg": "该api不支持免密调用", - }) + if !safemode { + if c.Query("api_key") == api_key { + result, err := handleRouterAPI(routernum, apipath) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()}) + return + } + c.JSON(http.StatusOK, result) + return + } else { + c.JSON(http.StatusUnauthorized, gin.H{"msg": "Authentication failed."}) + return + } + } + c.JSON(http.StatusForbidden, gin.H{"msg": "This API needs authentication."}) + return } }) - e.GET("/:routernum/_api/gettemperature", func(c echo.Context) error { + r.GET("/routerapi/:routernum/systemapi/gettemperature", func(c *gin.Context) { routernum, err := strconv.Atoi(c.Param("routernum")) logrus.Debug(tokens) if err != nil { - return c.JSON(http.StatusOK, map[string]interface{}{"code": 1100, "msg": "参数错误"}) + c.JSON(http.StatusBadRequest, gin.H{"msg": "Parameter error"}) + return } - status, cpu_tp, fanspeed, w24g_tp, w5g_tp := tp.GetTemperature(c, routernum, hardwares[routernum]) - if status { - return c.JSON(http.StatusOK, map[string]interface{}{ - "code": 0, - "cpu": cpu_tp, - "fanspeed": fanspeed, - "w24g": w24g_tp, - "w5g": w5g_tp, + result := tp.GetTemperature(c, routernum, hardwares[routernum], dev) + if result.Success { + c.JSON(http.StatusOK, gin.H{ + "data": result.Data, + "status": result.Status, }) + return } - return c.JSON(http.StatusOK, map[string]interface{}{ - "code": 1103, - "msg": "不支持该设备", - }) + c.JSON(http.StatusNotImplemented, gin.H{"msg": "This device is not supported"}) }) - e.GET("/api/v1/data", func(c echo.Context) error { - chart := c.QueryParam("chart") - dimensions := c.QueryParam("dimensions") - - ip := dev[netdata_routernum].IP - token := tokens[netdata_routernum] - cpuLoad, memAvailable, _, _, upSpeed, downSpeed, temperature, deviceOnline, _, _ := netdata.ProcessData(ip, token) + // r.GET("/api/v1/data", func(c *gin.Context) { + // chart := c.Query("chart") + // dimensions := c.Query("dimensions") - switch chart { - - case "system.cpu": - if routerunits[netdata_routernum] { - cpuLoad = int(GetCpuPercent() * 100) - } - data := netdata.GenerateArray("system.cpu", cpuLoad, starttime, "system.cpu", "system.cpu") - return c.JSON(http.StatusOK, data) - case "mem.available": - data := netdata.GenerateArray("mem.available", memAvailable, starttime, "avail", "MemAvailable") - return c.JSON(http.StatusOK, data) - case "device.online": - data := netdata.GenerateArray("device.online", deviceOnline, starttime, "online", "online") - return c.JSON(http.StatusOK, data) - case "net.eth0": - if dimensions == "received" { - data := netdata.GenerateArray("net.eth0", downSpeed, starttime, "received", "received") - return c.JSON(http.StatusOK, data) - } - if dimensions == "sent" { - data := netdata.GenerateArray("net.eth0", -upSpeed, starttime, "sent", "sent") - return c.JSON(http.StatusOK, data) - } - return c.String(http.StatusOK, "缺失参数") - case "sensors.temp_thermal_zone0_thermal_thermal_zone0": - data := netdata.GenerateArray("sensors.temp_thermal_zone0_thermal_thermal_zone0", temperature, starttime, "temperature", "temperature") - return c.JSON(http.StatusOK, data) - default: - return c.JSON(http.StatusOK, map[string]interface{}{ - "code": 1102, - "msg": "该图表数据不支持", - }) - } - }) - // 没用 - // e.GET("/api/v1/charts", func(c echo.Context) error { // ip := dev[netdata_routernum].IP // token := tokens[netdata_routernum] - // cpuLoad, memAvailable, memTotal, memUsage, upSpeed, downSpeed, temperature, deviceonline := netdata.ProcessData(ip, token) - // cpuLoadData := netdata.GenerateDataForAllMetrics("system.cpu", "cpu", "percentage", cpuLoad, "used") - // memAvailableData := netdata.GenerateDataForAllMetrics("mem.available", "mem", "bytes", memAvailable, "used") - // memTotalData := netdata.GenerateDataForAllMetrics("mem.total", "mem", "bytes", memTotal, "used") - // memUsageData := netdata.GenerateDataForAllMetrics("mem.used", "mem", "percentage", memUsage, "used") - // upSpeedData := netdata.GenerateDataForAllMetrics("net.eth0.receivedspeed", "net", "bytes", upSpeed, "received") - // downSpeedData := netdata.GenerateDataForAllMetrics("net.eth0.sentspeed", "net", "bytes", downSpeed, "sent") - // temperatureData := netdata.GenerateDataForAllMetrics("sensors.temp_thermal_zone0_thermal_thermal_zone0", "sensors", "celsius", temperature, "temperature") - // deviceonlineData := netdata.GenerateDataForAllMetrics("device.online", "device", "count", deviceonline, "online") - // charts := map[string]interface{}{ - // "system.cpu": cpuLoadData, - // "mem.available": memAvailableData, - // "mem.total": memTotalData, - // "mem.used": memUsageData, - // "net.eth0.receivedspeed": upSpeedData, - // "net.eth0.sentspeed": downSpeedData, - // "device.online": deviceonlineData, - // "sensors.temp_thermal_zone0_thermal_thermal_zone0": temperatureData, - // } - // data := map[string]interface{}{ - // "hostname": routerNames[netdata_routernum], - // "version": "v1.29.3", - // "release_channel": "stable", - // "os": "linux", - // "timezone": "Asia/Shanghai", - // "update_every": 1, - // "history": 3996, - // "memory_mode": "dbengine", - // "custom_info": "", - // "charts": charts, + // cpuLoad, memAvailable, _, _, upSpeed, downSpeed, temperature, deviceOnline, _, _ := netdata.ProcessData(ip, token) + + // switch chart { + // case "system.cpu": + // if routerunits[netdata_routernum] { + // cpuLoad = int(GetCpuPercent() * 100) + // } + // data := netdata.GenerateArray("system.cpu", cpuLoad, starttime, "system.cpu", "system.cpu") + // c.JSON(http.StatusOK, data) + // return + // case "mem.available": + // data := netdata.GenerateArray("mem.available", memAvailable, starttime, "avail", "MemAvailable") + // c.JSON(http.StatusOK, data) + // return + // case "device.online": + // data := netdata.GenerateArray("device.online", deviceOnline, starttime, "online", "online") + // c.JSON(http.StatusOK, data) + // return + // case "net.eth0": + // if dimensions == "received" { + // data := netdata.GenerateArray("net.eth0", downSpeed, starttime, "received", "received") + // c.JSON(http.StatusOK, data) + // return + // } + // if dimensions == "sent" { + // data := netdata.GenerateArray("net.eth0", -upSpeed, starttime, "sent", "sent") + // c.JSON(http.StatusOK, data) + // return + // } + // c.String(http.StatusOK, "缺失参数") + // return + // case "sensors.temp_thermal_zone0_thermal_thermal_zone0": + // data := netdata.GenerateArray("sensors.temp_thermal_zone0_thermal_thermal_zone0", temperature, starttime, "temperature", "temperature") + // c.JSON(http.StatusOK, data) + // return + // default: + // c.JSON(http.StatusOK, map[string]interface{}{ + // "code": 1102, + // "msg": "该图表数据不支持", + // }) + // return // } - - // return c.JSON(http.StatusOK, data) - // }) - // 应付HA用 - e.GET("/api/v1/allmetrics?format=json&help=no&types=no×tamps=yes&names=yes&data=average", func(c echo.Context) error { - ip := dev[netdata_routernum].IP - token := tokens[netdata_routernum] - cpuLoad, memAvailable, memTotal, memUsage, upSpeed, downSpeed, temperature, deviceonline, uploadtotal, downloadtotal := netdata.ProcessData(ip, token) - cpuLoadData := netdata.GenerateDataForAllMetrics("system.cpu", "cpu", "percentage", cpuLoad, "used") - memAvailableData := netdata.GenerateDataForAllMetrics("mem.available", "mem", "bytes", memAvailable, "used") - memTotalData := netdata.GenerateDataForAllMetrics("mem.total", "mem", "bytes", memTotal, "used") - memUsageData := netdata.GenerateDataForAllMetrics("mem.used", "mem", "percentage", memUsage, "used") - upSpeedData := netdata.GenerateDataForAllMetrics("net.eth0.receivedspeed", "net", "bytes", upSpeed, "received") - downSpeedData := netdata.GenerateDataForAllMetrics("net.eth0.sentspeed", "net", "bytes", downSpeed, "sent") - temperatureData := netdata.GenerateDataForAllMetrics("sensors.temp_thermal_zone0_thermal_thermal_zone0", "sensors", "celsius", temperature, "temperature") - deviceonlineData := netdata.GenerateDataForAllMetrics("device.online", "device", "count", deviceonline, "online") - uploadtotalData := netdata.GenerateDataForAllMetrics("net.eth0.sent", "net", "bytes", uploadtotal, "total") - downloadtotalData := netdata.GenerateDataForAllMetrics("net.eth0.received", "net", "bytes", downloadtotal, "total") - data := map[string]interface{}{ - "system.cpu": cpuLoadData, - "mem.available": memAvailableData, - "mem.total": memTotalData, - "mem.used": memUsageData, - "net.eth0.receivedspeed": upSpeedData, - "net.eth0.sentspeed": downSpeedData, - "device.online": deviceonlineData, - "net.eth0.sent": uploadtotalData, - "net.eth0.received": downloadtotalData, - "sensors.temp_thermal_zone0_thermal_thermal_zone0": temperatureData, - } + r.GET("/systemapi/getconfig", getconfig) - return c.JSON(http.StatusOK, data) + r.GET("/systemapi/getrouterhistory", func(c *gin.Context) { + routernum, err := strconv.Atoi(c.Query("routernum")) + fixupfloat := c.Query("fixupfloat") + if fixupfloat == "" { + fixupfloat = "false" + } + fixupfloat_bool, err1 := strconv.ParseBool(fixupfloat) + if err != nil || err1 != nil { + c.JSON(http.StatusBadRequest, gin.H{"msg": "Parameter error"}) + return + } + if !historyEnable { + c.JSON(http.StatusServiceUnavailable, gin.H{"msg": "History data is not enabled"}) + return + } + history := database.GetRouterHistory(databasepath, routernum, fixupfloat_bool) + c.JSON(http.StatusOK, gin.H{"history": history}) }) - // e.GET("/api/v1/alarms?all&format=json", func(c echo.Context) error { - // time := int(time.Now().Unix()) - // var value int - // var status string - // if login.CheckRouterAvailability(dev[netdata_routernum].IP) { - // value = 1 - // status = "CLEAR" - // } else { - // value = 0 - // status = "CRITICAL" - // } - // alarm := map[string]interface{}{ - // "id": 1, - // "name": "router_offline", - // "chart": "router.status", - // "family": "status", - // "active": true, - // "disabled": false, - // "silenced": false, - // "exec": "/usr/lib/netdata/plugins.d/alarm-notify.sh", - // "recipient": "sysadmin", - // "source": "10@/usr/lib/netdata/conf.d/health.d/router_offline.conf", - // "units": "status", - // "info": "the status of the router (offline = 0, online = 1)", - // "status": status, - // "last_status_change": 1704026010, - // "last_updated": time, - // "next_update": time + 10, - // "update_every": 10, - // "delay_up_duration": 0, - // "delay_down_duration": 300, - // "delay_max_duration": 3600, - // "delay_multiplier": 1.5, - // "delay": 0, - // "delay_up_to_timestamp": 1704026010, - // "warn_repeat_every": "0", - // "crit_repeat_every": "0", - // "value_string": "1", - // "last_repeat": "0", - // "calc": "${status}", - // "calc_parsed": "${status}", - // "warn": "$this == 0", - // "warn_parsed": "${this} == 0", - // "crit": "$this == 0", - // "crit_parsed": "${this} == 0", - // "green": nil, - // "red": nil, - // "value": value, - // } - // data := map[string]interface{}{ - // "hostname": routerNames[netdata_routernum], - // "latest_alarm_log_unique_id": 1703857080, - // "status": true, - // "now": time, - // "alarms": map[string]interface{}{ - // "router_offline": alarm, - // }, - // } - // return c.JSON(http.StatusOK, data) - // }) - e.GET("/_api/getconfig", getconfig) + r.GET("/systemapi/getdevicehistory", func(c *gin.Context) { + deviceMac := c.Query("devicemac") + fixupfloat := c.Query("fixupfloat") + if fixupfloat == "" { + fixupfloat = "false" + } + fixupfloat_bool, err := strconv.ParseBool(fixupfloat) - e.GET("/_api/gethistory", func(c echo.Context) error { - routernum, err := strconv.Atoi(c.QueryParam("routernum")) - if err != nil { - return c.JSON(http.StatusOK, map[string]interface{}{"code": 1100, "msg": "参数错误"}) + if deviceMac == "" || len(deviceMac) != 17 || err != nil { + c.JSON(http.StatusBadRequest, gin.H{"msg": "Parameter error"}) + return } if !historyEnable { - return c.JSON(http.StatusOK, map[string]interface{}{ - "code": 1101, - "msg": "历史数据未开启", - }) + c.JSON(http.StatusServiceUnavailable, gin.H{"msg": "History data is not enabled"}) + return } - history := database.Getdata(databasepath, routernum) + history := database.GetDeviceHistory(databasepath, deviceMac, fixupfloat_bool) - return c.JSON(http.StatusOK, map[string]interface{}{ - "code": 0, - "history": history, - }) + c.JSON(http.StatusOK, gin.H{"history": history}) }) - e.GET("/_api/flushstatic", func(c echo.Context) error { - err := download.DownloadStatic(basedirectory, true) + r.GET("/systemapi/flushstatic", func(c *gin.Context) { + // logrus.Debugln(c.Query("api_key")) + if c.Query("api_key") != api_key { + c.JSON(http.StatusUnauthorized, gin.H{"msg": "Authentication failed"}) + return + } + err := download.DownloadStatic(workdirectory, true, true) if err != nil { - return c.JSON(http.StatusOK, map[string]interface{}{ - "code": 1101, - "msg": err, - }) + c.JSON(http.StatusInternalServerError, gin.H{"msg": err}) + return } - logrus.Debugln("执行完成") - return c.JSON(http.StatusOK, map[string]interface{}{ - "code": 0, - "msg": "执行完成", - }) + logrus.Debugln("Execution completed") + c.JSON(http.StatusOK, gin.H{"msg": "Execution completed"}) }) - e.GET("/_api/refresh", func(c echo.Context) error { - go func() { - gettoken(dev) - }() - return c.JSON(http.StatusOK, map[string]interface{}{ - "code": 0, - "msg": "已开始刷新", - }) + r.GET("/systemapi/refresh", func(c *gin.Context) { + gettoken(dev) + logrus.Debugln("Execution completed") + c.JSON(http.StatusOK, gin.H{"msg": "Execution completed"}) }) - e.GET("/_api/quit", func(c echo.Context) error { + + r.GET("/systemapi/quit", func(c *gin.Context) { + if c.Query("api_key") != api_key { + c.JSON(http.StatusUnauthorized, gin.H{"msg": "Authentication failed"}) + return + } go func() { time.Sleep(1 * time.Second) defer os.Exit(0) }() - return c.JSON(http.StatusOK, map[string]interface{}{ - "code": 0, - "msg": "正在关闭", - }) + c.JSON(http.StatusOK, gin.H{"msg": "Shutting down"}) }) - // var contentHandler = echo.WrapHandler(http.FileServer(http.FS(static))) - // var contentRewrite = middleware.Rewrite(map[string]string{"/*": "/static/$1"}) - - // e.GET("/*", contentHandler, contentRewrite) - if tiny == false { - directory := "static" - if basedirectory != "" { - directory = filepath.Join(basedirectory, "static") - } - logrus.Debug("静态资源目录为:" + directory) - e.Static("/", directory) - } gettoken(dev) - e.Start(":" + fmt.Sprint(port)) database.CheckDatabase(databasepath) c.AddFunc("@every "+strconv.Itoa(flushTokenTime)+"s", func() { gettoken(dev) }) if historyEnable { - c.AddFunc("@every "+strconv.Itoa(sampletime)+"s", func() { database.Savetodb(databasepath, dev, tokens, maxsaved) }) + c.AddFunc("@every "+strconv.Itoa(sampletime)+"s", func() { + database.Savetodb(databasepath, dev, tokens, maxsaved) + }) } c.Start() @@ -467,6 +421,14 @@ func main() { go func() { <-quit - e.Close() + logrus.Info("Server is shutting down...") + + // Stop scheduled task + c.Stop() + + logrus.Info("Server closed") + os.Exit(0) }() + + r.Run(fmt.Sprintf("%s:%d", address, port)) } diff --git a/modules/config/base.go b/modules/config/base.go index 856ad3d..0f4174d 100644 --- a/modules/config/base.go +++ b/modules/config/base.go @@ -1,124 +1,135 @@ package config import ( - "encoding/json" + "errors" "flag" - "io" - . "main/modules/download" - "net/http" + "fmt" "os" "path/filepath" + "strconv" "time" + "github.com/Mirouterui/mirouter-ui/modules/download" "github.com/sirupsen/logrus" -) - -var ( - password string - key string - ip string - debug bool - port int - tiny bool - routerunit bool - configPath string - basedirectory string - databasepath string - historyEnable bool - dev []Dev - maxsaved int - flushTokenTime int - sampletime int - netdata_routernum int + "github.com/spf13/viper" ) type Dev struct { - Password string `json:"password"` - Key string `json:"key"` - IP string `json:"ip"` - RouterUnit bool `json:"routerunit"` + Password string `mapstructure:"password"` + Key string `mapstructure:"key"` + IP string `mapstructure:"ip"` + RouterUnit bool `mapstructure:"routerunit"` + IsLocal bool `mapstructure:"islocal"` } + type History struct { - Enable bool `json:"enable"` - MaxDeleted int `json:"maxsaved"` - Sampletime int `json:"sampletime"` + Enable bool `mapstructure:"enable"` + MaxDeleted int `mapstructure:"maxsaved"` + Sampletime int `mapstructure:"sampletime"` } -type Config struct { - Dev []Dev `json:"dev"` - History History `json:"history"` - Debug bool `json:"debug"` - Port int `json:"port"` - Tiny bool `json:"tiny"` - FlushTokenTime int `json:"flushTokenTime"` - Netdata_routernum int `json:"netdata_routernum"` + +type AppConfig struct { + Dev []Dev `mapstructure:"dev"` + History History `mapstructure:"history"` + Debug bool `mapstructure:"debug"` + Port int `mapstructure:"port"` + Address string `mapstructure:"address"` + Tiny bool `mapstructure:"tiny"` + FlushTokenTime int `mapstructure:"flushTokenTime"` + Netdata_routernum int `mapstructure:"netdata_routernum"` + Workdirectory string `mapstructure:"-"` + Databasepath string `mapstructure:"-"` + ApiKey string `mapstructure:"api_key"` + SafeMode bool `mapstructure:"safemode"` } -func GetConfigInfo() (dev []Dev, debug bool, port int, tiny bool, basedirectory string, flushTokenTime int, databasepath string, maxsaved int, historyEnable bool, sampletime int, netdata_routernum int) { - flag.StringVar(&configPath, "config", "", "配置文件路径") - flag.StringVar(&basedirectory, "basedirectory", "", "基础目录路径") - flag.StringVar(&databasepath, "databasepath", "", "数据库路径") +var ( + // Global configuration instance + Cfg *AppConfig + + // Command line parameters + configPath string + workdirectory string + databasepath string + autocheckupdate string +) + +func init() { + appPath, _ := os.Executable() + flag.StringVar(&configPath, "config", filepath.Join(filepath.Dir(appPath), "config.yaml"), "configuration file path") + flag.StringVar(&workdirectory, "workdirectory", "", "working directory path") + flag.StringVar(&databasepath, "databasepath", filepath.Join(filepath.Dir(appPath), "database.db"), "database path") + flag.StringVar(&autocheckupdate, "autocheckupdate", "true", "auto check updates") flag.Parse() - appPath, err := os.Executable() - checkErr(err) - if configPath == "" { - configPath = filepath.Join(filepath.Dir(appPath), "config.json") - } - if databasepath == "" { - databasepath = filepath.Join(filepath.Dir(appPath), "database.db") - } - logrus.Info("配置文件路径为:" + configPath) - data, err := os.ReadFile(configPath) - if err != nil { - logrus.Info("未找到配置文件,正在下载") - resp, err := http.Get("https://mrui-api.hzchu.top/downloadconfig") - checkErr(err) - defer resp.Body.Close() - out, err := os.Create(configPath) - checkErr(err) - defer out.Close() - _, err = io.Copy(out, resp.Body) - checkErr(err) - logrus.Info("下载配置文件完成,请修改配置文件") - logrus.Info("5秒后退出程序") - time.Sleep(5 * time.Second) - os.Exit(1) +} + +func LoadConfig() (*AppConfig, error) { + v := viper.New() + v.SetConfigFile(configPath) + v.SetConfigType("yaml") + + // Set default values + v.SetDefault("dev", []Dev{ + { + Password: "", + Key: "a2ffa5c9be07488bbb04a3a47d3c5f6a", + IP: "192.168.31.1", + RouterUnit: false, + IsLocal: false, + }, + }) + v.SetDefault("history.enable", false) + v.SetDefault("history.maxsaved", 3000) + v.SetDefault("history.sampletime", 86400) + v.SetDefault("debug", true) + v.SetDefault("port", 6789) + v.SetDefault("tiny", false) + v.SetDefault("flushTokenTime", 1800) + v.SetDefault("netdata_routernum", 0) + v.SetDefault("api_key", "") + v.SetDefault("safemode", true) + v.SetDefault("address", "0.0.0.0") + + // Support environment variable overrides + v.AutomaticEnv() + v.SetEnvPrefix("MIROUTERUI") + + // Read configuration file + if err := v.ReadInConfig(); err != nil { + if errors.Is(err, os.ErrNotExist) { + if err := v.WriteConfigAs(configPath); err != nil { + return nil, fmt.Errorf("failed to create default config file: %w", err) + } + logrus.Info("default config file created, please modify it and restart") + time.Sleep(5 * time.Second) + os.Exit(0) + } else { + return nil, fmt.Errorf("failed to read config file: %w", err) + } } - var config Config - err = json.Unmarshal(data, &config) - if err != nil { - logrus.Info("配置文件存在错误") + Cfg = &AppConfig{} + if err := v.Unmarshal(Cfg); err != nil { + return nil, fmt.Errorf("failed to parse config: %w", err) } - dev = config.Dev - debug = config.Debug - port = config.Port - tiny = config.Tiny - maxsaved = config.History.MaxDeleted - historyEnable = config.History.Enable - sampletime = config.History.Sampletime - flushTokenTime = config.FlushTokenTime - netdata_routernum = config.Netdata_routernum - // logrus.Info(password) - // logrus.Info(key) - if tiny == false { - DownloadStatic(basedirectory, false) + + Cfg.Workdirectory = workdirectory + Cfg.Databasepath = databasepath + + if len(Cfg.Dev) == 0 { + return nil, fmt.Errorf("router information not filled, please check config file") } - if debug == true { + + if Cfg.Debug { logrus.SetLevel(logrus.DebugLevel) } else { logrus.SetLevel(logrus.InfoLevel) } - numDevs := len(dev) - if numDevs == 0 { - logrus.Info("未填写路由器信息,请检查配置文件") - logrus.Info("5秒后退出程序") - time.Sleep(5 * time.Second) - os.Exit(1) - } - return dev, debug, port, tiny, basedirectory, flushTokenTime, databasepath, maxsaved, historyEnable, sampletime, netdata_routernum -} -func checkErr(err error) { - if err != nil { - panic(err) + // Check for updates + autocheckupdatebool, _ := strconv.ParseBool(autocheckupdate) + if !Cfg.Tiny { + download.DownloadStatic(Cfg.Workdirectory, false, autocheckupdatebool) } + + return Cfg, nil } diff --git a/modules/database/base.go b/modules/database/base.go index a9d66ec..cac4ac5 100644 --- a/modules/database/base.go +++ b/modules/database/base.go @@ -2,13 +2,18 @@ package database import ( "io" - "math" "strconv" "encoding/json" "fmt" - "main/modules/config" + + "github.com/shirou/gopsutil/v3/cpu" + + // "math" "net/http" + "time" + + "github.com/Mirouterui/mirouter-ui/modules/config" "github.com/glebarez/sqlite" "github.com/sirupsen/logrus" @@ -16,19 +21,38 @@ import ( ) // For database -type History struct { +type RouterHistory struct { gorm.Model Ip string RouterNum int - Cpu float64 + Cpu int Cpu_tp int - Mem float64 - UpSpeed float64 - DownSpeed float64 - UpTotal float64 - DownTotal float64 + Mem int + UpSpeed int + DownSpeed int + UpTotal int + DownTotal int DeviceNum int } +type DevicesHistory struct { + gorm.Model + Mac string + UpSpeed int + DownSpeed int + UpTotal int + DownTotal int +} +type DeviceInfo struct { + DevName string `json:"devname"` + DownloadTotal int `json:"download,string"` + DownSpeed int `json:"downspeed,string"` + Mac string `json:"mac"` + MaxDownloadSpeed int `json:"maxdownloadspeed,string"` + MaxUploadSpeed int `json:"maxuploadspeed,string"` + Online int `json:"online,string"` + UploadTotal int `json:"upload,string"` + UpSpeed int `json:"upspeed,string"` +} type Dev struct { Password string `json:"password"` @@ -40,84 +64,142 @@ type Dev struct { // CheckDatabase checks the SQLite database file at the given path. // // Parameters: -// - databasepath: a string variable that holds the path to the SQLite database file. +// - databasePath: a string variable that holds the path to the SQLite database file. // // Returns: None. -func CheckDatabase(databasepath string) { - // databasepath is a variable that holds the path to the SQLite database file - db, err := gorm.Open(sqlite.Open(databasepath), &gorm.Config{}) +func CheckDatabase(databasePath string) { + // databasePath is a variable that holds the path to the SQLite database file + db, err := gorm.Open(sqlite.Open(databasePath), &gorm.Config{}) checkErr(err) // Check if the history table exists, if not, create it - err = db.AutoMigrate(&History{}) + err = db.AutoMigrate(&RouterHistory{}) + checkErr(err) + err = db.AutoMigrate(&DevicesHistory{}) checkErr(err) - // Perform CRUD operations on the history table using db.Create, db.First, db.Update, db.Delete methods + defer func() { + sqlDB, err := db.DB() + checkErr(err) + sqlDB.Close() + }() } // Savetodb saves device statistics to the database. // // Parameters: -// - databasepath: the path to the database. +// - databasePath: the path to the database. // - dev: an array of device configurations. // - tokens: a map of token IDs to strings. // - maxsaved: the maximum number of records to delete. -func Savetodb(databasepath string, dev []config.Dev, tokens map[int]string, maxsaved int) { - db, err := gorm.Open(sqlite.Open(databasepath), &gorm.Config{}) +func Savetodb(databasePath string, dev []config.Dev, tokens map[int]string, maxsaved int) { + db, err := gorm.Open(sqlite.Open(databasePath), &gorm.Config{}) checkErr(err) + var ( + cpu int + cpu_tp int + mem int + upSpeed int + downSpeed int + upTotal int + downTotal int + deviceNum int + devs []interface{} + mac string + ) for i, d := range dev { ip := d.IP routerNum := i - cpu, cpu_tp, mem, upSpeed, downSpeed, upTotal, downTotal, deviceNum := getDeviceStats(i, tokens, ip) + cpu, cpu_tp, mem, upSpeed, downSpeed, upTotal, downTotal, deviceNum, devs = getRouterStats(i, tokens, ip) var count int64 - db.Model(&History{}).Where("router_num = ?", routerNum).Count(&count) + db.Model(&RouterHistory{}).Where("router_num = ?", routerNum).Count(&count) if count >= int64(maxsaved) { logrus.Debug("删除历史数据") - db.Exec("DELETE FROM histories WHERE router_num = ? AND created_at = (SELECT MIN(created_at) FROM histories WHERE router_num = ? );", routerNum, routerNum) - + db.Exec("DELETE FROM router_histories WHERE router_num = ? AND created_at = (SELECT MIN(created_at) FROM router_histories WHERE router_num = ? );", routerNum, routerNum) } - db.Create(&History{ + db.Create(&RouterHistory{ Ip: ip, RouterNum: routerNum, - Cpu: cpu, + Cpu: int(cpu), Cpu_tp: cpu_tp, - Mem: mem, - UpSpeed: upSpeed, - DownSpeed: downSpeed, - UpTotal: upTotal, - DownTotal: downTotal, + Mem: int(mem), + UpSpeed: int(upSpeed), + DownSpeed: int(downSpeed), + UpTotal: int(upTotal), + DownTotal: int(downTotal), DeviceNum: deviceNum, }) + + for _, dev := range devs { + devMap := dev.(map[string]interface{}) + + macVal, ok := devMap["mac"] + if !ok || macVal == "" { + continue + } + + data, err := json.Marshal(devMap) + // logrus.Debug("data: ", string(data)) + checkErr(err) + + var info DeviceInfo + err = json.Unmarshal(data, &info) + checkErr(err) + mac = info.Mac + upSpeed = int(info.UpSpeed) + downSpeed = int(info.DownSpeed) + upTotal = int(info.UploadTotal) + downTotal = int(info.DownloadTotal) + db.Create(&DevicesHistory{ + Mac: mac, + UpSpeed: int(upSpeed), + DownSpeed: int(downSpeed), + UpTotal: int(upTotal), + DownTotal: int(downTotal), + }) + db.Model(&DevicesHistory{}).Where("mac = ?", mac).Count(&count) + if count >= int64(maxsaved) { + logrus.Debug("删除历史数据") + db.Exec("DELETE FROM devices_histories WHERE mac = ? AND created_at = (SELECT MIN(created_at) FROM devices_histories WHERE mac = ? );", mac, mac) + } + } } + defer func() { + sqlDB, err := db.DB() + checkErr(err) + sqlDB.Close() + }() } -func Getdata(databasepath string, routernum int) []History { - db, err := gorm.Open(sqlite.Open(databasepath), &gorm.Config{}) +func GetRouterHistory(databasePath string, routernum int, fixupfloat bool) []RouterHistory { + db, err := gorm.Open(sqlite.Open(databasePath), &gorm.Config{}) checkErr(err) - var history []History + var history []RouterHistory db.Where("router_num = ?", routernum).Find(&history) + defer func() { + sqlDB, err := db.DB() + checkErr(err) + sqlDB.Close() + }() return history } -// getDeviceStats retrieves the device statistics from the specified router. -// -// Parameters: -// - routernum: The router number. -// - tokens: A map containing the tokens. -// - ip: The IP address of the router. -// -// Returns: -// - cpuload: The Cpu load. -// - cpu_tp: The Cpu temperature. -// - memusage: The memory usage. -// - upspeed: The upload speed. -// - downspeed: The download speed. -// - uploadtotal: The total upload amount. -// - downloadtotal: The total download amount. -// - devicenum_now: The number of online devices. -func getDeviceStats(routernum int, tokens map[int]string, ip string) (float64, int, float64, float64, float64, float64, float64, int) { +func GetDeviceHistory(databasePath string, deviceMac string, fixupfloat bool) []DevicesHistory { + db, err := gorm.Open(sqlite.Open(databasePath), &gorm.Config{}) + checkErr(err) + var history []DevicesHistory + db.Where("mac = ?", deviceMac).Find(&history) + defer func() { + sqlDB, err := db.DB() + checkErr(err) + sqlDB.Close() + }() + return history +} + +func getRouterStats(routernum int, tokens map[int]string, ip string) (int, int, int, int, int, int, int, int, []interface{}) { if tokens[routernum] == "" { - return 0, 0, 0, 0, 0, 0, 0, 0 + return 0, 0, 0, 0, 0, 0, 0, 0, []interface{}{} } url := fmt.Sprintf("http://%s/cgi-bin/luci/;stok=%s/api/misystem/status", ip, tokens[routernum]) resp, err := http.Get(url) @@ -127,24 +209,49 @@ func getDeviceStats(routernum int, tokens map[int]string, ip string) (float64, i var result map[string]interface{} json.Unmarshal(body, &result) - upspeed, _ := strconv.ParseFloat(result["wan"].(map[string]interface{})["upspeed"].(string), 64) - downspeed, _ := strconv.ParseFloat(result["wan"].(map[string]interface{})["downspeed"].(string), 64) - uploadtotal, _ := strconv.ParseFloat(result["wan"].(map[string]interface{})["upload"].(string), 64) - downloadtotal, _ := strconv.ParseFloat(result["wan"].(map[string]interface{})["download"].(string), 64) - cpuload := roundToOneDecimal(result["cpu"].(map[string]interface{})["load"].(float64) * 100) + upspeedTemp, _ := strconv.ParseFloat(result["wan"].(map[string]interface{})["upspeed"].(string), 64) + downspeedTemp, _ := strconv.ParseFloat(result["wan"].(map[string]interface{})["downspeed"].(string), 64) + uploadtotalTemp, _ := strconv.ParseFloat(result["wan"].(map[string]interface{})["upload"].(string), 64) + downloadtotalTemp, _ := strconv.ParseFloat(result["wan"].(map[string]interface{})["download"].(string), 64) + upspeed := int(upspeedTemp) + downspeed := int(downspeedTemp) + uploadtotal := int(uploadtotalTemp) + downloadtotal := int(downloadtotalTemp) + + cpuload := int(result["cpu"].(map[string]interface{})["load"].(float64) * 100) cpu_tp := int(result["temperature"].(float64)) - memusage := roundToOneDecimal(result["mem"].(map[string]interface{})["usage"].(float64) * 100) + memusage := int(result["mem"].(map[string]interface{})["usage"].(float64) * 100) devicenum_now := int(result["count"].(map[string]interface{})["online"].(float64)) + devs := result["dev"].([]interface{}) - return cpuload, cpu_tp, memusage, upspeed, downspeed, uploadtotal, downloadtotal, devicenum_now + return cpuload, cpu_tp, memusage, upspeed, downspeed, uploadtotal, downloadtotal, devicenum_now, devs } -func roundToOneDecimal(num float64) float64 { - return math.Round(num*100) / 100 -} +// func roundToOneDecimal(num int) int { +// return math.Round(num*100) / 100 +// } func checkErr(err error) { if err != nil { logrus.Debug(err) } } +func GetCpuPercent() float64 { + percent, _ := cpu.Percent(time.Second, false) + return percent[0] / 100 +} + +// func round(val int, roundOn int, places int) (newVal int) { +// var rounder int +// pow := math.Pow(10, int(places)) +// intermed := val * pow +// _, frac := math.Modf(intermed) + +// if frac >= roundOn { +// rounder = math.Ceil(intermed) +// } else { +// rounder = math.Floor(intermed) +// } +// newVal = rounder / pow +// return +// } diff --git a/modules/download/base.go b/modules/download/base.go index 5df3ad2..46b6276 100644 --- a/modules/download/base.go +++ b/modules/download/base.go @@ -17,27 +17,27 @@ var ( Version string ) -func DownloadStatic(basedirectory string, force bool) error { +func DownloadStatic(workdirectory string, force bool, checkupdate bool) error { directory := "static" - if basedirectory != "" { - directory = filepath.Join(basedirectory, "static") + if workdirectory != "" { + directory = filepath.Join(workdirectory, "static") } if force { - //删除 + // Delete os.RemoveAll(directory) } _, err := os.Stat(directory) if os.IsNotExist(err) || force { - logrus.Info("正从'Mirouterui/static'下载静态资源") + logrus.Info("Downloading static resources from 'Mirouterui/static'") downloadfile(directory) return nil } - // 读取/static/version/index.html + // Read /static/version/index.html f, err := os.Open(filepath.Join(directory, "version", "index.html")) if err != nil { - logrus.Info("无法读取静态资源版本号,重新下载") + logrus.Info("Failed to read static resource version, downloading again") os.RemoveAll(directory) downloadfile(directory) return err @@ -46,34 +46,47 @@ func DownloadStatic(basedirectory string, force bool) error { defer f.Close() forntendVersion, err := io.ReadAll(f) checkErr(err) - logrus.Info("静态资源已存在,版本号为" + string(forntendVersion)) + logrus.Info("Static resources already exist, version: " + string(forntendVersion)) - resp, err := http.Get("https://mrui-api.hzchu.top/checkupdate") + // Check for updates + if checkupdate { + resp, err := http.Get("https://mrui-api.hzchu.top/v2/api/checkupdate") - if err != nil { - logrus.Info("无法获取更新信息,跳过检查") - return err - } - - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - checkErr(err) - var result map[string]interface{} - json.Unmarshal(body, &result) + if err != nil { + logrus.Info("Failed to get update information, skipping check") + return err + } - if result["backversion"] != string(Version) { - message := fmt.Sprintf("后端程序发现新版本(%v),请及时更新", result["backversion"]) - logrus.Info(message) - } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + checkErr(err) + var result map[string]interface{} + json.Unmarshal(body, &result) + front := result["front"].(map[string]interface{}) + frontversion := front["version"] + frontchangelog := front["changelog"] + + backend := result["backend"].(map[string]interface{}) + backendversion := backend["version"] + backendchangelog := front["changelog"] + + if backendversion != Version { + message := fmt.Sprintf("Backend program found new version (%v), please update. Changelog: %v", backendversion, backendchangelog) + logrus.Info(message) + } - if result["frontversion"] != string(forntendVersion) { - message := fmt.Sprintf("前端文件发现新版本(%v),正在重新下载", result["frontversion"]) - logrus.Info(message) - os.RemoveAll(directory) - downloadfile(directory) + if frontversion != string(forntendVersion) { + message := fmt.Sprintf("Frontend files found new version (%v), update in the frontend page. Changelog: %v", frontversion, frontchangelog) + logrus.Info(message) + os.RemoveAll(directory) + downloadfile(directory) + } + } else { + logrus.Info("Skipping update check") } return nil } + func downloadfile(directory string) { resp, err := http.Get("http://mrui-api.hzchu.top/downloadstatic") checkErr(err) @@ -93,7 +106,7 @@ func downloadfile(directory string) { func unzip(src, dest string) error { r, err := zip.OpenReader(src) if err != nil { - logrus.Info("静态资源下载失败,请尝试手动下载") + logrus.Info("Static resource download failed, please try manual download") return err } defer r.Close() @@ -120,6 +133,7 @@ func unzip(src, dest string) error { return err } _, err = io.Copy(outFile, rc) + checkErr(err) outFile.Close() } diff --git a/modules/login/base.go b/modules/login/base.go index 3f39fca..1fb1999 100644 --- a/modules/login/base.go +++ b/modules/login/base.go @@ -27,7 +27,7 @@ var ( func createNonce() string { typeVar := 0 - deviceID := "" //无效参数 + deviceID := "00:e0:4f:27:3d:09" //MAC timeVar := int(time.Now().Unix()) randomVar := rand.Intn(10000) return fmt.Sprintf("%d_%s_%d_%d", typeVar, deviceID, timeVar, randomVar) @@ -81,18 +81,18 @@ func getrouterinfo(ip string) (bool, string, string) { //提取routername routername = data["routername"].(string) hardware = data["hardware"].(string) - logrus.Debug("路由器型号为:" + hardware) - logrus.Debug("路由器名称为:" + routername) + logrus.Debug("Router model: " + hardware) + logrus.Debug("Router name: " + routername) // 检查 newEncryptMode newEncryptMode, ok := data["newEncryptMode"].(float64) if !ok { - logrus.Debug("使用旧加密模式") + logrus.Debug("Using old encryption mode") return false, routername, hardware } if newEncryptMode != 0 { - logrus.Debug("使用新加密模式") - logrus.Info("当前路由器可能无法正常获取某些数据!") + logrus.Debug("Using new encryption mode") + logrus.Info("The current router may not be able to fetch certain data properly!") return true, routername, hardware } return false, routername, hardware @@ -105,20 +105,20 @@ func CheckRouterAvailability(ip string) bool { _, err := client.Get("http://" + ip) if err != nil { - logrus.Info("路由器" + ip + "不可用,请检查配置或路由器状态") + logrus.Info("Router " + ip + " is not available, please check configuration or router status") return false } return true } func GetToken(password string, key string, ip string) (string, string, string) { - logrus.Debug("检查路由器可用性...") + logrus.Debug("Checking router availability...") if !CheckRouterAvailability(ip) { - return "", "路由器不可用", "" + return "", "Router is not available", "" } - logrus.Debug("获取路由器信息...") + logrus.Debug("Getting router information...") newEncryptMode, routername, hardware := getrouterinfo(ip) - logrus.Info("更新token...") + logrus.Info("Updating token...") nonce := createNonce() var hashedPassword string @@ -137,7 +137,7 @@ func GetToken(password string, key string, ip string) (string, string, string) { resp, err := http.PostForm(ourl, params) if err != nil { - logrus.Info("登录失败,请检查配置或路由器状态") + logrus.Info("Login failed, please check configuration or router status") logrus.Info(err) time.Sleep(5 * time.Second) os.Exit(1) @@ -150,16 +150,16 @@ func GetToken(password string, key string, ip string) (string, string, string) { if result["code"] != nil { code = int(result["code"].(float64)) } else { - logrus.Info("路由器登录请求返回值为空!请检查配置") + logrus.Info("Router login request returned empty! Please check configuration") } if code == 0 { - logrus.Debug("当前token为:" + fmt.Sprint(result["token"])) + logrus.Debug("Current token: " + fmt.Sprint(result["token"])) token = result["token"].(string) } else { - logrus.Info("登录失败,请检查配置,以下为返回输出:") + logrus.Info("Login failed, please check configuration, the following is the return output:") logrus.Info(string(body)) - logrus.Info("5秒后退出程序") + logrus.Info("Exiting program in 5 seconds") time.Sleep(5 * time.Second) os.Exit(1) } diff --git a/modules/tp/base.go b/modules/tp/base.go index 009a32f..5b4b38d 100644 --- a/modules/tp/base.go +++ b/modules/tp/base.go @@ -2,65 +2,163 @@ package tp import ( "encoding/json" - "main/modules/config" "os/exec" + "regexp" + "strconv" + "strings" + + "github.com/Mirouterui/mirouter-ui/modules/config" - "github.com/labstack/echo/v4" "github.com/sirupsen/logrus" ) var ( - hardware string - dev []config.Dev - cpu_cmd *exec.Cmd - w24g_cmd *exec.Cmd - w5g_cmd *exec.Cmd + cpuCmd *exec.Cmd + w24gCmd *exec.Cmd + w5gCmd *exec.Cmd ) +// TemperatureData 存储温度数据 +type TemperatureData struct { + CPU int `json:"cpu"` + FanSpeed int `json:"fanspeed"` + W24G int `json:"w24g"` + W5G int `json:"w5g"` +} + +// TemperatureStatus 存储温度数据的状态 +type TemperatureStatus struct { + CPU bool `json:"cpu"` + FanSpeed bool `json:"fanspeed"` + W24G bool `json:"w24g"` + W5G bool `json:"w5g"` +} + +// TemperatureResult 返回的温度结果 +type TemperatureResult struct { + Success bool `json:"success"` + Data TemperatureData `json:"data"` + Status TemperatureStatus `json:"status"` +} + // 获取温度 -func GetTemperature(c echo.Context, routernum int, hardware string) (bool, string, string, string, string) { - if dev[routernum].RouterUnit == false { - return false, "-233", "-233", "-233", "-233" +func GetTemperature(c interface{}, routerNum int, hardware string, dev []config.Dev) TemperatureResult { + result := TemperatureResult{ + Success: false, + Data: TemperatureData{ + CPU: 0, + FanSpeed: 0, + W24G: 0, + W5G: 0, + }, + Status: TemperatureStatus{ + CPU: false, + FanSpeed: false, + W24G: false, + W5G: false, + }, + } + + if !dev[routerNum].IsLocal { + return result } - var cpu_out, w24g_out, w5g_out []byte - var err1, err2, err3 error - var cpu_tp, fanspeed, w24g_tp, w5g_tp string + + var cpuOut, w24gOut, w5gOut []byte + var cpuErr, w24gErr, w5gErr error + var cpuTemp, fanSpeed, w24gTemp, w5gTemp string + switch hardware { + case "CR8809": + cpuCmd = exec.Command("cat", "/sys/class/thermal/thermal_zone0/temp") + w5gCmd = exec.Command("cat", "/sys/class/ieee80211/phy0/device/net/wifi0/thermal/temp") + cpuOut, cpuErr = cpuCmd.Output() + w5gOut, w5gErr = w5gCmd.Output() + cpuTemp = string(cpuOut) + fanSpeed = "0" + w24gTemp = "0" + w5gTemp = string(w5gOut) case "RA69": - cpu_cmd = exec.Command("cat", "/sys/class/thermal/thermal_zone0/temp") - w24g_cmd = exec.Command("cat", "/sys/class/ieee80211/phy0/device/net/wifi1/thermal/temp") - w5g_cmd = exec.Command("cat", "/sys/class/ieee80211/phy0/device/net/wifi0/thermal/temp") - cpu_out, err1 = cpu_cmd.Output() - w24g_out, err2 = w24g_cmd.Output() - w5g_out, err3 = w5g_cmd.Output() - - cpu_tp = string(cpu_out) - fanspeed = "-233" - w24g_tp = string(w24g_out) - w5g_tp = string(w5g_out) + cpuCmd = exec.Command("cat", "/sys/class/thermal/thermal_zone0/temp") + w24gCmd = exec.Command("cat", "/sys/class/ieee80211/phy0/device/net/wifi1/thermal/temp") + w5gCmd = exec.Command("cat", "/sys/class/ieee80211/phy0/device/net/wifi0/thermal/temp") + cpuOut, cpuErr = cpuCmd.Output() + w24gOut, w24gErr = w24gCmd.Output() + w5gOut, w5gErr = w5gCmd.Output() + + cpuTpTemp, _ := strconv.Atoi(strings.TrimSpace(string(cpuOut))) + cpuTemp = strconv.Itoa(cpuTpTemp / 1000) + fanSpeed = "0" + w24gTemp = string(w24gOut) + w5gTemp = string(w5gOut) case "R1D": - type Ubus_data struct { + type ubusData struct { Fanspeed string `json:"fanspeed"` Temperature string `json:"temperature"` } - cpu_cmd = exec.Command("ubus", "call", "rmonitor", "status") - cpu_out, err1 = cpu_cmd.Output() - var data Ubus_data - err := json.Unmarshal(cpu_out, &data) + cpuCmd = exec.Command("ubus", "call", "rmonitor", "status") + cpuOut, cpuErr = cpuCmd.Output() + var data ubusData + err := json.Unmarshal(cpuOut, &data) if err != nil { - logrus.Error("获取温度失败,报错信息为" + err.Error()) + logrus.Error("Failed to get temperature, error message: " + err.Error()) } - cpu_tp = data.Temperature - fanspeed = data.Fanspeed - w24g_tp = "-233" - w5g_tp = "-233" + cpuTemp = data.Temperature + fanSpeed = data.Fanspeed + w24gTemp = "0" + w5gTemp = "0" + case "RB06": + cpuCmd = exec.Command("cat", "/sys/class/thermal/thermal_zone0/temp") + w24gCmd = exec.Command("iwpriv", "wl0", "stat", "|", "grep", "CurrentTemperature") + w5gCmd = exec.Command("iwpriv", "wl1", "stat", "|", "grep", "CurrentTemperature") + cpuOut, cpuErr = cpuCmd.Output() + w24gOut, w24gErr = w24gCmd.Output() + w5gOut, w5gErr = w5gCmd.Output() + + cpuTemp = string(cpuOut) + re := regexp.MustCompile(`CurrentTemperature\s*=\s*(\d+)`) + matches24g := re.FindStringSubmatch(string(w24gOut)) + if len(matches24g) > 1 { + w24gTemp = matches24g[1] + } else { + w24gTemp = "0" + } + matches5g := re.FindStringSubmatch(string(w5gOut)) + if len(matches5g) > 1 { + w5gTemp = matches5g[1] + } else { + w5gTemp = "0" + } + fanSpeed = "0" default: - return false, "-233", "-233", "-233", "-233" + return result + } + + // 处理错误并记录日志 + if cpuErr != nil || w24gErr != nil || w5gErr != nil { + logrus.Error("Failed to get temperature, error message: " + cpuErr.Error() + w24gErr.Error() + w5gErr.Error()) + } + + // 转换温度数据并更新状态 + if cpuTemp != "" { + result.Data.CPU, _ = strconv.Atoi(strings.ReplaceAll(cpuTemp, "\n", "")) + result.Status.CPU = cpuErr == nil + } + + if fanSpeed != "" { + result.Data.FanSpeed, _ = strconv.Atoi(strings.ReplaceAll(fanSpeed, "\n", "")) + result.Status.FanSpeed = true + } + + if w24gTemp != "" { + result.Data.W24G, _ = strconv.Atoi(strings.ReplaceAll(w24gTemp, "\n", "")) + result.Status.W24G = w24gErr == nil } - if err1 != nil || err2 != nil || err3 != nil { - logrus.Error("获取温度失败,报错信息为" + err1.Error() + err2.Error() + err3.Error()) + if w5gTemp != "" { + result.Data.W5G, _ = strconv.Atoi(strings.ReplaceAll(w5gTemp, "\n", "")) + result.Status.W5G = w5gErr == nil } - return true, cpu_tp, fanspeed, w24g_tp, w5g_tp + result.Success = true + return result } diff --git a/otherfile/images/config.png b/otherfile/images/config.png deleted file mode 100644 index aa2fbb8..0000000 Binary files a/otherfile/images/config.png and /dev/null differ diff --git a/otherfile/images/history_index.png b/otherfile/images/history_index.png deleted file mode 100644 index fc68f16..0000000 Binary files a/otherfile/images/history_index.png and /dev/null differ diff --git a/otherfile/images/logo.png b/otherfile/images/logo.png new file mode 100644 index 0000000..05ebcda Binary files /dev/null and b/otherfile/images/logo.png differ