├── .github └── workflows │ ├── buildapp-dev.yml │ ├── buildapp.yml │ ├── buildappwhr.yml │ └── builddocker.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── build.sh ├── config.json ├── go.mod ├── go.sum ├── main.go ├── modules ├── config │ └── base.go ├── database │ └── base.go ├── download │ └── base.go ├── login │ └── base.go ├── netdata │ └── base.go └── tp │ └── base.go └── otherfile ├── downpage.html └── images └── logo.png /.github/workflows/buildapp-dev.yml: -------------------------------------------------------------------------------- 1 | name: Build DEV version 2 | run-name: Build DEV version 3 | 4 | permissions: write-all 5 | 6 | on: 7 | workflow_dispatch: 8 | push: 9 | branches: 10 | - main 11 | 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-20.04 16 | steps: 17 | 18 | - name: Checkout code 19 | uses: actions/checkout@v3 20 | 21 | - name: Set up Go 22 | uses: actions/setup-go@v4 23 | with: 24 | go-version: '1.21' 25 | 26 | - name: Install UPX 27 | run: sudo apt-get install upx 28 | 29 | - name: download modules 30 | run: go mod download 31 | 32 | - name: Run build script 33 | # 传入commit sha 34 | run: bash build.sh ${{ github.sha }} 35 | 36 | # 安装 rclone 37 | # dev版本不另外发release 38 | - name: Install rclone 39 | run: | 40 | cd ~ 41 | curl https://rclone.org/install.sh | sudo bash 42 | # 配置 rclone 43 | - name: Configure rclone 44 | run: | 45 | mkdir -p ~/.config/rclone 46 | cat > ~/.config/rclone/rclone.conf << EOF 47 | ${{ secrets.RCLONECONFIG }} 48 | EOF 49 | - name: Sync to OneDrive 50 | run: | 51 | sudo timedatectl set-timezone "Asia/Shanghai" 52 | rclone mkdir one:/share/Mirouter-ui/dev/${{ github.sha }} 53 | rclone sync ./build one:/share/Mirouter-ui/dev/${{ github.sha }} 54 | 55 | builddocker: 56 | runs-on: ubuntu-20.04 57 | steps: 58 | - 59 | name: Checkout 60 | uses: actions/checkout@v3 61 | - 62 | name: Set up QEMU 63 | uses: docker/setup-qemu-action@v2 64 | - 65 | name: Set up Docker Buildx 66 | uses: docker/setup-buildx-action@v2 67 | - 68 | name: Login to Docker Hub 69 | uses: docker/login-action@v2 70 | with: 71 | username: ${{ secrets.DOCKERHUB_USERNAME }} 72 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 73 | - 74 | name: Build and push 75 | uses: docker/build-push-action@v4 76 | with: 77 | context: . 78 | platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 79 | push: true 80 | tags: thun888/mirouter-ui:dev 81 | build-args: | 82 | VERSION=${{ github.sha }} 83 | -------------------------------------------------------------------------------- /.github/workflows/buildapp.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | run-name: Build ${{ github.event.inputs.version }} by @${{ github.actor }} 3 | 4 | permissions: write-all 5 | 6 | on: 7 | workflow_dispatch: 8 | inputs: 9 | version: 10 | description: '版本号' 11 | required: true 12 | 13 | 14 | jobs: 15 | build_win: 16 | runs-on: ubuntu-20.04 17 | steps: 18 | 19 | - name: Checkout code 20 | uses: actions/checkout@v3 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@v4 24 | with: 25 | go-version: '1.21' 26 | 27 | - name: Install UPX 28 | run: sudo apt-get install upx 29 | 30 | - name: download modules 31 | run: go mod download 32 | 33 | - name: Run build script 34 | run: bash build.sh ${{ github.event.inputs.version }} win 35 | 36 | - name: upload artifact 37 | uses: actions/upload-artifact@v3 38 | with: 39 | name: mirouter-ui-win 40 | path: ./build/** 41 | 42 | build_linux: 43 | runs-on: ubuntu-20.04 44 | steps: 45 | 46 | - name: Checkout code 47 | uses: actions/checkout@v3 48 | 49 | - name: Set up Go 50 | uses: actions/setup-go@v4 51 | with: 52 | go-version: '1.21' 53 | 54 | - name: Install UPX 55 | run: sudo apt-get install upx 56 | 57 | - name: download modules 58 | run: go mod download 59 | 60 | - name: Run build script 61 | run: bash build.sh ${{ github.event.inputs.version }} linux 62 | 63 | - name: upload artifact 64 | uses: actions/upload-artifact@v3 65 | with: 66 | name: mirouter-ui-linux 67 | path: ./build/** 68 | 69 | build_darwin: 70 | runs-on: ubuntu-20.04 71 | steps: 72 | - name: Checkout code 73 | uses: actions/checkout@v3 74 | 75 | - name: Set up Go 76 | uses: actions/setup-go@v4 77 | with: 78 | go-version: '1.21' 79 | 80 | - name: Install UPX 81 | run: sudo apt-get install upx 82 | 83 | - name: download modules 84 | run: go mod download 85 | 86 | - name: Run build script 87 | run: bash build.sh ${{ github.event.inputs.version }} darwin 88 | 89 | - name: upload artifact 90 | uses: actions/upload-artifact@v3 91 | with: 92 | name: mirouter-ui-darwin 93 | path: ./build/** 94 | 95 | post_release: 96 | runs-on: ubuntu-20.04 97 | needs: [build_win, build_linux, build_darwin] 98 | steps: 99 | - name: Download artifacts 100 | uses: actions/download-artifact@v3 101 | with: 102 | name: mirouter-ui-win 103 | path: ./build 104 | - name: Download artifacts 105 | uses: actions/download-artifact@v3 106 | with: 107 | name: mirouter-ui-linux 108 | path: ./build 109 | - name: Download artifacts 110 | uses: actions/download-artifact@v3 111 | with: 112 | name: mirouter-ui-darwin 113 | path: ./build 114 | 115 | - name: Upload Release Assets 116 | uses: ncipollo/release-action@v1 117 | with: 118 | artifacts: "build/*" 119 | token: ${{ secrets.TOKEN }} 120 | tag: ${{ github.event.inputs.version }} 121 | name: Release ${{ github.event.inputs.version }} 122 | draft: true 123 | 124 | # 安装 rclone 125 | - name: Install rclone 126 | run: | 127 | cd ~ 128 | curl https://rclone.org/install.sh | sudo bash 129 | # 配置 rclone 130 | - name: Configure rclone 131 | run: | 132 | mkdir -p ~/.config/rclone 133 | cat > ~/.config/rclone/rclone.conf << EOF 134 | ${{ secrets.RCLONECONFIG }} 135 | EOF 136 | - name: Sync to OneDrive 137 | run: | 138 | sudo timedatectl set-timezone "Asia/Shanghai" 139 | rclone mkdir one:/share/Mirouter-ui/${{ github.event.inputs.version }} 140 | rclone sync ./build one:/share/Mirouter-ui/${{ github.event.inputs.version }} 141 | builddocker: 142 | runs-on: ubuntu-20.04 143 | steps: 144 | - name: Checkout 145 | uses: actions/checkout@v3 146 | - name: Set up QEMU 147 | uses: docker/setup-qemu-action@v2 148 | - name: Set up Docker Buildx 149 | uses: docker/setup-buildx-action@v2 150 | - name: Login to Docker Hub 151 | uses: docker/login-action@v2 152 | with: 153 | username: ${{ secrets.DOCKERHUB_USERNAME }} 154 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 155 | - name: Build and push 156 | uses: docker/build-push-action@v4 157 | with: 158 | context: . 159 | platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 160 | push: true 161 | tags: thun888/mirouter-ui:latest 162 | build-args: | 163 | VERSION=${{ github.event.inputs.version }} 164 | -------------------------------------------------------------------------------- /.github/workflows/buildappwhr.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release without history record 2 | run-name: Build ${{ github.event.inputs.version }} without history record by @${{ github.actor }} 3 | 4 | permissions: write-all 5 | 6 | on: 7 | workflow_dispatch: 8 | inputs: 9 | version: 10 | description: '版本号' 11 | required: true 12 | 13 | 14 | jobs: 15 | build_win: 16 | runs-on: ubuntu-20.04 17 | steps: 18 | 19 | - name: Checkout code 20 | uses: actions/checkout@v3 21 | with: 22 | ref: whr 23 | 24 | - name: Set up Go 25 | uses: actions/setup-go@v4 26 | with: 27 | go-version: '1.21' 28 | 29 | - name: Install UPX 30 | run: sudo apt-get install upx 31 | 32 | - name: download modules 33 | run: go mod download 34 | 35 | - name: Run build script 36 | run: bash build.sh ${{ github.event.inputs.version }} win 37 | 38 | - name: upload artifact 39 | uses: actions/upload-artifact@v3 40 | with: 41 | name: mirouter-ui-win 42 | path: ./build/** 43 | 44 | build_linux: 45 | runs-on: ubuntu-20.04 46 | steps: 47 | 48 | - name: Checkout code 49 | uses: actions/checkout@v3 50 | 51 | - name: Set up Go 52 | uses: actions/setup-go@v4 53 | with: 54 | go-version: '1.21' 55 | 56 | - name: Install UPX 57 | run: sudo apt-get install upx 58 | 59 | - name: download modules 60 | run: go mod download 61 | 62 | - name: Run build script 63 | run: bash build.sh ${{ github.event.inputs.version }} linux 64 | 65 | - name: upload artifact 66 | uses: actions/upload-artifact@v3 67 | with: 68 | name: mirouter-ui-linux 69 | path: ./build/** 70 | 71 | build_darwin: 72 | runs-on: ubuntu-20.04 73 | steps: 74 | - name: Checkout code 75 | uses: actions/checkout@v3 76 | 77 | - name: Set up Go 78 | uses: actions/setup-go@v4 79 | with: 80 | go-version: '1.21' 81 | 82 | - name: Install UPX 83 | run: sudo apt-get install upx 84 | 85 | - name: download modules 86 | run: go mod download 87 | 88 | - name: Run build script 89 | run: bash build.sh ${{ github.event.inputs.version }} darwin 90 | 91 | - name: upload artifact 92 | uses: actions/upload-artifact@v3 93 | with: 94 | name: mirouter-ui-darwin 95 | path: ./build/** 96 | 97 | post_release: 98 | runs-on: ubuntu-20.04 99 | needs: [build_win, build_linux, build_darwin] 100 | steps: 101 | - name: Download artifacts 102 | uses: actions/download-artifact@v3 103 | with: 104 | name: mirouter-ui-win 105 | path: ./build 106 | - name: Download artifacts 107 | uses: actions/download-artifact@v3 108 | with: 109 | name: mirouter-ui-linux 110 | path: ./build 111 | - name: Download artifacts 112 | uses: actions/download-artifact@v3 113 | with: 114 | name: mirouter-ui-darwin 115 | path: ./build 116 | 117 | - name: Upload Release Assets 118 | uses: ncipollo/release-action@v1 119 | with: 120 | artifacts: "build/*" 121 | token: ${{ secrets.TOKEN }} 122 | tag: ${{ github.event.inputs.version }}whr 123 | name: Release ${{ github.event.inputs.version }} without history record 124 | draft: true 125 | 126 | # 安装 rclone 127 | - name: Install rclone 128 | run: | 129 | cd ~ 130 | curl https://rclone.org/install.sh | sudo bash 131 | 132 | # 配置 rclone 133 | - name: Configure rclone 134 | run: | 135 | mkdir -p ~/.config/rclone 136 | cat > ~/.config/rclone/rclone.conf << EOF 137 | ${{ secrets.RCLONECONFIG }} 138 | EOF 139 | - name: Sync to OneDrive 140 | run: | 141 | sudo timedatectl set-timezone "Asia/Shanghai" 142 | rclone mkdir one:/share/Mirouter-ui/whr/${{ github.event.inputs.version }} 143 | rclone sync ./build one:/share/Mirouter-ui/whr/${{ github.event.inputs.version }} 144 | 145 | builddocker: 146 | runs-on: ubuntu-20.04 147 | steps: 148 | - 149 | name: Checkout 150 | uses: actions/checkout@v3 151 | - 152 | name: Set up QEMU 153 | uses: docker/setup-qemu-action@v2 154 | - 155 | name: Set up Docker Buildx 156 | uses: docker/setup-buildx-action@v2 157 | - 158 | name: Login to Docker Hub 159 | uses: docker/login-action@v2 160 | with: 161 | username: ${{ secrets.DOCKERHUB_USERNAME }} 162 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 163 | - 164 | name: Build and push 165 | uses: docker/build-push-action@v4 166 | with: 167 | context: . 168 | platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 169 | push: true 170 | tags: thun888/mirouter-ui:whr 171 | -------------------------------------------------------------------------------- /.github/workflows/builddocker.yml: -------------------------------------------------------------------------------- 1 | name: build-docker 2 | 3 | on: 4 | repository_dispatch: 5 | types: [mrui-release] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - 13 | name: Checkout 14 | uses: actions/checkout@v3 15 | - 16 | name: Set up QEMU 17 | uses: docker/setup-qemu-action@v2 18 | - 19 | name: Set up Docker Buildx 20 | uses: docker/setup-buildx-action@v2 21 | - 22 | name: Login to Docker Hub 23 | uses: docker/login-action@v2 24 | with: 25 | username: ${{ secrets.DOCKERHUB_USERNAME }} 26 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 27 | - 28 | name: Build and push 29 | uses: docker/build-push-action@v4 30 | with: 31 | context: . 32 | platforms: linux/amd64,linux/386,linux/arm64 33 | push: true 34 | tags: thun888/mirouter-ui:latest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build/ 3 | dist/ 4 | main.exe 5 | aa.sh 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG VERSION 2 | 3 | FROM golang:1.21.0-alpine3.18 AS builder 4 | 5 | WORKDIR /app 6 | 7 | COPY . . 8 | 9 | RUN go mod download 10 | 11 | RUN go build -ldflags "-X 'main.Version=$VERSION'" -o main . 12 | 13 | FROM alpine:3.18 14 | 15 | WORKDIR /app 16 | 17 | COPY --from=builder /app/main /app/main 18 | 19 | EXPOSE 6789 20 | 21 | CMD ["./main","--config=/app/data/config.json","--workdirectory=/app/data/","--databasepath=/app/data/database.db","--autocheckupdate=false"] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | ## Mirouter-ui 4 | 5 | > 😎 基于小米路由器API的展示面板 6 | 7 | [](https://hub.docker.com/r/thun888/mirouter-ui) 8 | [](http://hits.dwyl.com/Mirouterui/mirouter-ui) 9 | [](https://github.com/Mirouterui/mirouter-ui/actions/workflows/buildapp.yml) 10 | [](https://github.com/Mirouterui/mirouter-ui/actions/workflows/buildapp-dev.yml) 11 | 12 | 13 | 14 | 部署请移步至[官方网站](https://mrui.hzchu.top/) 15 | 16 | 17 | 18 | ## 状态 19 | 20 |  21 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | if [ -z "$1" ] 2 | then 3 | read -p "版本号: " VERSION 4 | else 5 | VERSION=$1 6 | fi 7 | echo "版本号是:$VERSION" 8 | echo "Building MirouterUI $VERSION..." 9 | OUTPUT_DIR="./build" 10 | echo "编译生成目录:$OUTPUT_DIR" 11 | mkdir -p $OUTPUT_DIR 12 | 13 | if [ -z "$2" ] || [ "$2" = "win" ] 14 | then 15 | echo "Building win_amd64" 16 | GOOS=windows GOARCH=amd64 go build -ldflags "-X 'main.Version=$VERSION'" -o $OUTPUT_DIR/mirouterui_win_amd64_noupx_$VERSION.exe main.go 17 | upx --best -o $OUTPUT_DIR/mirouterui_win_amd64_$VERSION.exe $OUTPUT_DIR/mirouterui_win_amd64_noupx_$VERSION.exe 18 | 19 | echo "Building win_386" 20 | GOOS=windows GOARCH=386 go build -ldflags "-X 'main.Version=$VERSION'" -o $OUTPUT_DIR/mirouterui_win_386_noupx_$VERSION.exe main.go 21 | upx --best -o $OUTPUT_DIR/mirouterui_win_386_$VERSION.exe $OUTPUT_DIR/mirouterui_win_386_noupx_$VERSION.exe 22 | 23 | echo "Building win_arm64" 24 | GOOS=windows GOARCH=arm64 go build -ldflags "-X 'main.Version=$VERSION'" -o $OUTPUT_DIR/mirouterui_win_arm64_noupx_$VERSION.exe main.go 25 | 26 | # rm main.exe 27 | fi 28 | 29 | if [ -z "$2" ] || [ "$2" = "linux" ] 30 | then 31 | echo "Building linux_amd64" 32 | GOOS=linux GOARCH=amd64 go build -ldflags "-X 'main.Version=$VERSION'" -o $OUTPUT_DIR/mirouterui_linux_amd64_noupx_$VERSION main.go 33 | upx --best -o $OUTPUT_DIR/mirouterui_linux_amd64_$VERSION $OUTPUT_DIR/mirouterui_linux_amd64_noupx_$VERSION 34 | 35 | echo "Building linux_arm64" 36 | GOOS=linux GOARCH=arm64 go build -ldflags "-X 'main.Version=$VERSION'" -o $OUTPUT_DIR/mirouterui_linux_arm64_noupx_$VERSION main.go 37 | upx --best -o $OUTPUT_DIR/mirouterui_linux_arm64_$VERSION $OUTPUT_DIR/mirouterui_linux_arm64_noupx_$VERSION 38 | 39 | echo "Building linux_mipsle" 40 | GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "-X 'main.Version=$VERSION'" -o $OUTPUT_DIR/mirouterui_linux_mipsle_noupx_$VERSION main.go 41 | upx --best -o $OUTPUT_DIR/mirouterui_linux_mipsle_$VERSION $OUTPUT_DIR/mirouterui_linux_mipsle_noupx_$VERSION 42 | 43 | echo "Building linux_mips" 44 | GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "-X 'main.Version=$VERSION'" -o $OUTPUT_DIR/mirouterui_linux_mips_noupx_$VERSION main.go 45 | upx --best -o $OUTPUT_DIR/mirouterui_linux_mips_$VERSION $OUTPUT_DIR/mirouterui_linux_mips_noupx_$VERSION 46 | 47 | echo "Building linux_s390x" 48 | GOOS=linux GOARCH=s390x go build -ldflags "-X 'main.Version=$VERSION'" -o $OUTPUT_DIR/mirouterui_linux_s390x_noupx_$VERSION main.go 49 | # upx --best -o $OUTPUT_DIR/mirouterui_linux_s390x_$VERSION $OUTPUT_DIR/mirouterui_linux_s390x_noupx_$VERSION 50 | for version in 5 6 7 51 | do 52 | echo "Building linux_armv$version" 53 | GOOS=linux GOARCH=arm GOARM=$version GOMIPS=softfloat go build -ldflags "-X 'main.Version=$VERSION'" -o $OUTPUT_DIR/mirouterui_linux_armv${version}_noupx_$VERSION main.go 54 | upx --best -o $OUTPUT_DIR/mirouterui_linux_armv${version}_$VERSION $OUTPUT_DIR/mirouterui_linux_armv${version}_noupx_$VERSION 55 | done 56 | fi 57 | 58 | 59 | if [ -z "$2" ] || [ "$2" = "darwin" ] 60 | then 61 | # Building darwin_amd64 62 | echo "Building darwin_amd64" 63 | GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'main.Version=$VERSION'" -o $OUTPUT_DIR/mirouterui_darwin_amd64_noupx_$VERSION main.go 64 | upx --best -o $OUTPUT_DIR/mirouterui_darwin_amd64_$VERSION $OUTPUT_DIR/mirouterui_darwin_amd64_noupx_$VERSION 65 | 66 | # Building darwin_arm64 67 | echo "Building darwin_arm64" 68 | GOOS=darwin GOARCH=arm64 go build -ldflags "-X 'main.Version=$VERSION'" -o $OUTPUT_DIR/mirouterui_darwin_arm64_noupx_$VERSION main.go 69 | upx --best -o $OUTPUT_DIR/mirouterui_darwin_arm64_$VERSION $OUTPUT_DIR/mirouterui_darwin_arm64_noupx_$VERSION 70 | fi -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "dev": [{ 3 | "password": "", 4 | "key": "a2ffa5c9be07488bbb04a3a47d3c5f6a", 5 | "ip": "192.168.31.1", 6 | "routerunit": false 7 | }], 8 | "history": { 9 | "enable": false, 10 | "sampletime": 300, 11 | "maxsaved": 8640 12 | }, 13 | "netdata_routerid": 0, 14 | "flushTokenTime": 1800, 15 | "tiny": false, 16 | "port": 6789, 17 | "debug": true 18 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/labstack/echo/v4 v4.11.1 7 | github.com/shirou/gopsutil v3.21.11+incompatible 8 | github.com/sirupsen/logrus v1.9.3 9 | gorm.io/gorm v1.25.2 10 | ) 11 | 12 | require ( 13 | github.com/dustin/go-humanize v1.0.1 // indirect 14 | github.com/glebarez/go-sqlite v1.21.2 // indirect 15 | github.com/google/uuid v1.3.0 // indirect 16 | github.com/jinzhu/inflection v1.0.0 // indirect 17 | github.com/jinzhu/now v1.1.5 // indirect 18 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 19 | modernc.org/libc v1.22.5 // indirect 20 | modernc.org/mathutil v1.5.0 // indirect 21 | modernc.org/memory v1.5.0 // indirect 22 | modernc.org/sqlite v1.23.1 // indirect 23 | ) 24 | 25 | require ( 26 | github.com/glebarez/sqlite v1.9.0 27 | github.com/go-ole/go-ole v1.2.6 // indirect 28 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect 29 | github.com/labstack/gommon v0.4.0 // indirect 30 | github.com/mattn/go-colorable v0.1.13 // indirect 31 | github.com/mattn/go-isatty v0.0.19 // indirect 32 | github.com/patrickmn/go-cache v2.1.0+incompatible 33 | github.com/robfig/cron/v3 v3.0.1 34 | github.com/tklauser/go-sysconf v0.3.12 // indirect 35 | github.com/tklauser/numcpus v0.6.1 // indirect 36 | github.com/valyala/bytebufferpool v1.0.0 // indirect 37 | github.com/valyala/fasttemplate v1.2.2 // indirect 38 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 39 | golang.org/x/crypto v0.11.0 // indirect 40 | golang.org/x/net v0.12.0 // indirect 41 | golang.org/x/sys v0.11.0 // indirect 42 | golang.org/x/text v0.11.0 // indirect 43 | golang.org/x/time v0.3.0 // indirect 44 | ) 45 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 5 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 6 | github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= 7 | github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= 8 | github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs= 9 | github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw= 10 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 11 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 12 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 13 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 14 | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= 15 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 16 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 17 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 18 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 19 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 20 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 21 | github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= 22 | github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= 23 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= 24 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 25 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 26 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 27 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 28 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 29 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 30 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 31 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 32 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 33 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 34 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 35 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 36 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 37 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 38 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 39 | github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= 40 | github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 41 | github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= 42 | github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 43 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 44 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 45 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 46 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 47 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 48 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 49 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 50 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= 51 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 52 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 53 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 54 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 55 | github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 56 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 57 | github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= 58 | github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 59 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= 60 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= 61 | golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= 62 | golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 63 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 64 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 65 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 66 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 67 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 68 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 69 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 70 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 71 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= 72 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 73 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= 74 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 75 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 76 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 77 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 78 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 79 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 80 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 81 | gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= 82 | gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= 83 | modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= 84 | modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= 85 | modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= 86 | modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 87 | modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= 88 | modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= 89 | modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= 90 | modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= 91 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | _ "flag" 6 | "fmt" 7 | "io" 8 | "main/modules/config" 9 | "main/modules/database" 10 | "main/modules/download" 11 | login "main/modules/login" 12 | "main/modules/netdata" 13 | "main/modules/tp" 14 | "net/http" 15 | 16 | // _ "net/http/pprof" 17 | "os" 18 | "os/exec" 19 | "os/signal" 20 | "path/filepath" 21 | "strconv" 22 | "syscall" 23 | "time" 24 | 25 | "github.com/labstack/echo/v4" 26 | "github.com/labstack/echo/v4/middleware" 27 | "github.com/robfig/cron/v3" 28 | "github.com/shirou/gopsutil/cpu" 29 | "github.com/sirupsen/logrus" 30 | ) 31 | 32 | var ( 33 | password string 34 | key string 35 | token string 36 | tokens map[int]string 37 | debug bool 38 | port int 39 | routerName string 40 | routerNames map[int]string 41 | hardware string 42 | hardwares map[int]string 43 | routerunits map[int]bool 44 | tiny bool 45 | routerunit bool 46 | dev []config.Dev 47 | cpu_cmd *exec.Cmd 48 | w24g_cmd *exec.Cmd 49 | w5g_cmd *exec.Cmd 50 | configPath string 51 | workdirectory string 52 | Version string 53 | databasepath string 54 | flushTokenTime int 55 | maxsaved int 56 | historyEnable bool 57 | sampletime int 58 | netdata_routernum int 59 | ) 60 | 61 | type Config struct { 62 | Dev []config.Dev `json:"dev"` 63 | Debug bool `json:"debug"` 64 | Port int `json:"port"` 65 | Tiny bool `json:"tiny"` 66 | Databasepath string `json:"databasepath"` 67 | } 68 | 69 | func init() { 70 | dev, debug, port, tiny, workdirectory, flushTokenTime, databasepath, maxsaved, historyEnable, sampletime, netdata_routernum = config.GetConfigInfo() 71 | tokens = make(map[int]string) 72 | routerNames = make(map[int]string) 73 | hardwares = make(map[int]string) 74 | routerunits = make(map[int]bool) 75 | // go func() { 76 | // logrus.Println(http.ListenAndServe(":6060", nil)) 77 | // }() 78 | } 79 | func GetCpuPercent() float64 { 80 | percent, _ := cpu.Percent(time.Second, false) 81 | return percent[0] / 100 82 | } 83 | 84 | func getconfig(c echo.Context) error { 85 | type DevNoPassword struct { 86 | Key string `json:"key"` 87 | IP string `json:"ip"` 88 | RouterUnit bool `json:"routerunit"` 89 | } 90 | type History struct { 91 | Enable bool `json:"enable"` 92 | MaxDeleted int `json:"maxsaved"` 93 | Databasepath string `json:"databasepath"` 94 | Sampletime int `json:"sampletime"` 95 | } 96 | devsNoPassword := []DevNoPassword{} 97 | for _, d := range dev { 98 | devNoPassword := DevNoPassword{ 99 | Key: d.Key, 100 | IP: d.IP, 101 | RouterUnit: d.RouterUnit, 102 | } 103 | devsNoPassword = append(devsNoPassword, devNoPassword) 104 | } 105 | history := History{} 106 | history.Enable = historyEnable 107 | history.MaxDeleted = maxsaved 108 | history.Databasepath = databasepath 109 | history.Sampletime = sampletime 110 | return c.JSON(http.StatusOK, map[string]interface{}{ 111 | "code": 0, 112 | "tiny": tiny, 113 | "port": port, 114 | "debug": debug, 115 | // "token": token, 116 | "dev": devsNoPassword, 117 | "history": history, 118 | "flushTokenTime": flushTokenTime, 119 | "ver": Version, 120 | }) 121 | } 122 | 123 | func gettoken(dev []config.Dev) { 124 | for i, d := range dev { 125 | token, routerName, hardware := login.GetToken(d.Password, d.Key, d.IP) 126 | tokens[i] = token 127 | routerNames[i] = routerName 128 | hardwares[i] = hardware 129 | routerunits[i] = d.RouterUnit 130 | logrus.Debug(hardwares[i]) 131 | } 132 | } 133 | func main() { 134 | starttime := int(time.Now().Unix()) 135 | logrus.Info("当前后端版本为:" + Version) 136 | e := echo.New() 137 | c := cron.New() 138 | e.Use(middleware.Recover()) 139 | // 输出访问日志 140 | if debug { 141 | e.Use(middleware.Logger()) 142 | } 143 | // e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { 144 | // return func(c echo.Context) error { 145 | // c.Response().Header().Set("Access-Control-Allow-Private-Network", "true") 146 | // return next(c) 147 | // } 148 | // }) 149 | 150 | e.Use(middleware.CORS()) 151 | 152 | e.GET("/:routernum/api/:apipath", func(c echo.Context) error { 153 | routernum, err := strconv.Atoi(c.Param("routernum")) 154 | if err != nil { 155 | return c.JSON(http.StatusOK, map[string]interface{}{"code": 1100, "msg": "参数错误"}) 156 | } 157 | apipath := c.Param("apipath") 158 | ip := dev[routernum].IP 159 | 160 | switch apipath { 161 | 162 | case "xqsystem/router_name": 163 | return c.JSON(http.StatusOK, map[string]interface{}{"code": 0, "routerName": routerNames[routernum]}) 164 | 165 | case "misystem/status", "misystem/devicelist", "xqsystem/internet_connect", "xqsystem/fac_info", "misystem/messages", "xqsystem/upnp", "xqnetwork/diagdevicelist": 166 | url := fmt.Sprintf("http://%s/cgi-bin/luci/;stok=%s/api/%s", ip, tokens[routernum], apipath) 167 | resp, err := http.Get(url) 168 | if err != nil { 169 | return c.JSON(http.StatusOK, map[string]interface{}{ 170 | "code": 1101, 171 | "msg": "小米路由器的api调用出错,请检查配置或路由器状态", 172 | }) 173 | } 174 | defer resp.Body.Close() 175 | body, _ := io.ReadAll(resp.Body) 176 | var result map[string]interface{} 177 | json.Unmarshal(body, &result) 178 | 179 | if routerunits[routernum] && apipath == "misystem/status" { 180 | cpuPercent := GetCpuPercent() 181 | if cpu, ok := result["cpu"].(map[string]interface{}); ok { 182 | cpu["load"] = cpuPercent 183 | } 184 | } 185 | return c.JSON(http.StatusOK, result) 186 | 187 | default: 188 | return c.JSON(http.StatusOK, map[string]interface{}{ 189 | "code": 1102, 190 | "msg": "该api不支持免密调用", 191 | }) 192 | } 193 | }) 194 | 195 | e.GET("/:routernum/_api/gettemperature", func(c echo.Context) error { 196 | routernum, err := strconv.Atoi(c.Param("routernum")) 197 | logrus.Debug(tokens) 198 | if err != nil { 199 | return c.JSON(http.StatusOK, map[string]interface{}{"code": 1100, "msg": "参数错误"}) 200 | } 201 | status, cpu_tp, fanspeed, w24g_tp, w5g_tp := tp.GetTemperature(c, routernum, hardwares[routernum], dev) 202 | if status { 203 | return c.JSON(http.StatusOK, map[string]interface{}{ 204 | "code": 0, 205 | "cpu": cpu_tp, 206 | "fanspeed": fanspeed, 207 | "w24g": w24g_tp, 208 | "w5g": w5g_tp, 209 | }) 210 | } 211 | return c.JSON(http.StatusOK, map[string]interface{}{ 212 | "code": 1103, 213 | "msg": "不支持该设备", 214 | }) 215 | }) 216 | 217 | e.GET("/api/v1/data", func(c echo.Context) error { 218 | chart := c.QueryParam("chart") 219 | dimensions := c.QueryParam("dimensions") 220 | 221 | ip := dev[netdata_routernum].IP 222 | token := tokens[netdata_routernum] 223 | cpuLoad, memAvailable, _, _, upSpeed, downSpeed, temperature, deviceOnline, _, _ := netdata.ProcessData(ip, token) 224 | 225 | switch chart { 226 | 227 | case "system.cpu": 228 | if routerunits[netdata_routernum] { 229 | cpuLoad = int(GetCpuPercent() * 100) 230 | } 231 | data := netdata.GenerateArray("system.cpu", cpuLoad, starttime, "system.cpu", "system.cpu") 232 | return c.JSON(http.StatusOK, data) 233 | case "mem.available": 234 | data := netdata.GenerateArray("mem.available", memAvailable, starttime, "avail", "MemAvailable") 235 | return c.JSON(http.StatusOK, data) 236 | case "device.online": 237 | data := netdata.GenerateArray("device.online", deviceOnline, starttime, "online", "online") 238 | return c.JSON(http.StatusOK, data) 239 | case "net.eth0": 240 | if dimensions == "received" { 241 | data := netdata.GenerateArray("net.eth0", downSpeed, starttime, "received", "received") 242 | return c.JSON(http.StatusOK, data) 243 | } 244 | if dimensions == "sent" { 245 | data := netdata.GenerateArray("net.eth0", -upSpeed, starttime, "sent", "sent") 246 | return c.JSON(http.StatusOK, data) 247 | } 248 | return c.String(http.StatusOK, "缺失参数") 249 | case "sensors.temp_thermal_zone0_thermal_thermal_zone0": 250 | data := netdata.GenerateArray("sensors.temp_thermal_zone0_thermal_thermal_zone0", temperature, starttime, "temperature", "temperature") 251 | return c.JSON(http.StatusOK, data) 252 | default: 253 | return c.JSON(http.StatusOK, map[string]interface{}{ 254 | "code": 1102, 255 | "msg": "该图表数据不支持", 256 | }) 257 | } 258 | }) 259 | // 没用 260 | // e.GET("/api/v1/charts", func(c echo.Context) error { 261 | // ip := dev[netdata_routernum].IP 262 | // token := tokens[netdata_routernum] 263 | // cpuLoad, memAvailable, memTotal, memUsage, upSpeed, downSpeed, temperature, deviceonline := netdata.ProcessData(ip, token) 264 | // cpuLoadData := netdata.GenerateDataForAllMetrics("system.cpu", "cpu", "percentage", cpuLoad, "used") 265 | // memAvailableData := netdata.GenerateDataForAllMetrics("mem.available", "mem", "bytes", memAvailable, "used") 266 | // memTotalData := netdata.GenerateDataForAllMetrics("mem.total", "mem", "bytes", memTotal, "used") 267 | // memUsageData := netdata.GenerateDataForAllMetrics("mem.used", "mem", "percentage", memUsage, "used") 268 | // upSpeedData := netdata.GenerateDataForAllMetrics("net.eth0.receivedspeed", "net", "bytes", upSpeed, "received") 269 | // downSpeedData := netdata.GenerateDataForAllMetrics("net.eth0.sentspeed", "net", "bytes", downSpeed, "sent") 270 | // temperatureData := netdata.GenerateDataForAllMetrics("sensors.temp_thermal_zone0_thermal_thermal_zone0", "sensors", "celsius", temperature, "temperature") 271 | // deviceonlineData := netdata.GenerateDataForAllMetrics("device.online", "device", "count", deviceonline, "online") 272 | // charts := map[string]interface{}{ 273 | // "system.cpu": cpuLoadData, 274 | // "mem.available": memAvailableData, 275 | // "mem.total": memTotalData, 276 | // "mem.used": memUsageData, 277 | // "net.eth0.receivedspeed": upSpeedData, 278 | // "net.eth0.sentspeed": downSpeedData, 279 | // "device.online": deviceonlineData, 280 | // "sensors.temp_thermal_zone0_thermal_thermal_zone0": temperatureData, 281 | // } 282 | // data := map[string]interface{}{ 283 | // "hostname": routerNames[netdata_routernum], 284 | // "version": "v1.29.3", 285 | // "release_channel": "stable", 286 | // "os": "linux", 287 | // "timezone": "Asia/Shanghai", 288 | // "update_every": 1, 289 | // "history": 3996, 290 | // "memory_mode": "dbengine", 291 | // "custom_info": "", 292 | // "charts": charts, 293 | // } 294 | 295 | // return c.JSON(http.StatusOK, data) 296 | 297 | // }) 298 | 299 | // 应付HA用 300 | e.GET("/api/v1/allmetrics?format=json&help=no&types=no×tamps=yes&names=yes&data=average", func(c echo.Context) error { 301 | ip := dev[netdata_routernum].IP 302 | token := tokens[netdata_routernum] 303 | cpuLoad, memAvailable, memTotal, memUsage, upSpeed, downSpeed, temperature, deviceonline, uploadtotal, downloadtotal := netdata.ProcessData(ip, token) 304 | cpuLoadData := netdata.GenerateDataForAllMetrics("system.cpu", "cpu", "percentage", cpuLoad, "used") 305 | memAvailableData := netdata.GenerateDataForAllMetrics("mem.available", "mem", "bytes", memAvailable, "used") 306 | memTotalData := netdata.GenerateDataForAllMetrics("mem.total", "mem", "bytes", memTotal, "used") 307 | memUsageData := netdata.GenerateDataForAllMetrics("mem.used", "mem", "percentage", memUsage, "used") 308 | upSpeedData := netdata.GenerateDataForAllMetrics("net.eth0.sentspeed", "net", "bytes", upSpeed, "sent") 309 | downSpeedData := netdata.GenerateDataForAllMetrics("net.eth0.receivedspeed", "net", "bytes", downSpeed, "received") 310 | temperatureData := netdata.GenerateDataForAllMetrics("sensors.temp_thermal_zone0_thermal_thermal_zone0", "sensors", "celsius", temperature, "temperature") 311 | deviceonlineData := netdata.GenerateDataForAllMetrics("device.online", "device", "count", deviceonline, "online") 312 | uploadtotalData := netdata.GenerateDataForAllMetrics("net.eth0.sent", "net", "bytes", uploadtotal, "total") 313 | downloadtotalData := netdata.GenerateDataForAllMetrics("net.eth0.received", "net", "bytes", downloadtotal, "total") 314 | data := map[string]interface{}{ 315 | "system.cpu": cpuLoadData, 316 | "mem.available": memAvailableData, 317 | "mem.total": memTotalData, 318 | "mem.used": memUsageData, 319 | "net.eth0.receivedspeed": downSpeedData, 320 | "net.eth0.sentspeed": upSpeedData, 321 | "device.online": deviceonlineData, 322 | "net.eth0.sent": uploadtotalData, 323 | "net.eth0.received": downloadtotalData, 324 | "sensors.temp_thermal_zone0_thermal_thermal_zone0": temperatureData, 325 | } 326 | 327 | return c.JSON(http.StatusOK, data) 328 | 329 | }) 330 | // e.GET("/api/v1/alarms?all&format=json", func(c echo.Context) error { 331 | // time := int(time.Now().Unix()) 332 | // var value int 333 | // var status string 334 | // if login.CheckRouterAvailability(dev[netdata_routernum].IP) { 335 | // value = 1 336 | // status = "CLEAR" 337 | // } else { 338 | // value = 0 339 | // status = "CRITICAL" 340 | // } 341 | // alarm := map[string]interface{}{ 342 | // "id": 1, 343 | // "name": "router_offline", 344 | // "chart": "router.status", 345 | // "family": "status", 346 | // "active": true, 347 | // "disabled": false, 348 | // "silenced": false, 349 | // "exec": "/usr/lib/netdata/plugins.d/alarm-notify.sh", 350 | // "recipient": "sysadmin", 351 | // "source": "10@/usr/lib/netdata/conf.d/health.d/router_offline.conf", 352 | // "units": "status", 353 | // "info": "the status of the router (offline = 0, online = 1)", 354 | // "status": status, 355 | // "last_status_change": 1704026010, 356 | // "last_updated": time, 357 | // "next_update": time + 10, 358 | // "update_every": 10, 359 | // "delay_up_duration": 0, 360 | // "delay_down_duration": 300, 361 | // "delay_max_duration": 3600, 362 | // "delay_multiplier": 1.5, 363 | // "delay": 0, 364 | // "delay_up_to_timestamp": 1704026010, 365 | // "warn_repeat_every": "0", 366 | // "crit_repeat_every": "0", 367 | // "value_string": "1", 368 | // "last_repeat": "0", 369 | // "calc": "${status}", 370 | // "calc_parsed": "${status}", 371 | // "warn": "$this == 0", 372 | // "warn_parsed": "${this} == 0", 373 | // "crit": "$this == 0", 374 | // "crit_parsed": "${this} == 0", 375 | // "green": nil, 376 | // "red": nil, 377 | // "value": value, 378 | // } 379 | 380 | // data := map[string]interface{}{ 381 | // "hostname": routerNames[netdata_routernum], 382 | // "latest_alarm_log_unique_id": 1703857080, 383 | // "status": true, 384 | // "now": time, 385 | // "alarms": map[string]interface{}{ 386 | // "router_offline": alarm, 387 | // }, 388 | // } 389 | // return c.JSON(http.StatusOK, data) 390 | // }) 391 | e.GET("/_api/getconfig", getconfig) 392 | 393 | e.GET("/_api/getrouterhistory", func(c echo.Context) error { 394 | routernum, err := strconv.Atoi(c.QueryParam("routernum")) 395 | fixupfloat := c.QueryParam("fixupfloat") 396 | if fixupfloat == "" { 397 | fixupfloat = "false" 398 | } 399 | fixupfloat_bool, err1 := strconv.ParseBool(fixupfloat) 400 | if err != nil || err1 != nil { 401 | return c.JSON(http.StatusOK, map[string]interface{}{"code": 1100, "msg": "参数错误"}) 402 | } 403 | if !historyEnable { 404 | return c.JSON(http.StatusOK, map[string]interface{}{ 405 | "code": 1101, 406 | "msg": "历史数据未开启", 407 | }) 408 | } 409 | history := database.GetRouterHistory(databasepath, routernum, fixupfloat_bool) 410 | 411 | return c.JSON(http.StatusOK, map[string]interface{}{ 412 | "code": 0, 413 | "history": history, 414 | }) 415 | }) 416 | 417 | e.GET("/_api/getdevicehistory", func(c echo.Context) error { 418 | deviceMac := c.QueryParam("devicemac") 419 | fixupfloat := c.QueryParam("fixupfloat") 420 | if fixupfloat == "" { 421 | fixupfloat = "false" 422 | } 423 | fixupfloat_bool, err := strconv.ParseBool(fixupfloat) 424 | 425 | if deviceMac == "" || len(deviceMac) != 17 || err != nil { 426 | return c.JSON(http.StatusOK, map[string]interface{}{"code": 1100, "msg": "参数错误"}) 427 | } 428 | if !historyEnable { 429 | return c.JSON(http.StatusOK, map[string]interface{}{ 430 | "code": 1101, 431 | "msg": "历史数据未开启", 432 | }) 433 | } 434 | history := database.GetDeviceHistory(databasepath, deviceMac, fixupfloat_bool) 435 | 436 | return c.JSON(http.StatusOK, map[string]interface{}{ 437 | "code": 0, 438 | "history": history, 439 | }) 440 | }) 441 | e.GET("/_api/flushstatic", func(c echo.Context) error { 442 | err := download.DownloadStatic(workdirectory, true, true) 443 | if err != nil { 444 | return c.JSON(http.StatusOK, map[string]interface{}{ 445 | "code": 1101, 446 | "msg": err, 447 | }) 448 | } 449 | logrus.Debugln("执行完成") 450 | return c.JSON(http.StatusOK, map[string]interface{}{ 451 | "code": 0, 452 | "msg": "执行完成", 453 | }) 454 | }) 455 | 456 | e.GET("/_api/refresh", func(c echo.Context) error { 457 | gettoken(dev) 458 | logrus.Debugln("执行完成") 459 | return c.JSON(http.StatusOK, map[string]interface{}{ 460 | "code": 0, 461 | "msg": "执行完成", 462 | }) 463 | }) 464 | e.GET("/_api/quit", func(c echo.Context) error { 465 | go func() { 466 | time.Sleep(1 * time.Second) 467 | defer os.Exit(0) 468 | }() 469 | return c.JSON(http.StatusOK, map[string]interface{}{ 470 | "code": 0, 471 | "msg": "正在关闭", 472 | }) 473 | }) 474 | 475 | // var contentHandler = echo.WrapHandler(http.FileServer(http.FS(static))) 476 | // var contentRewrite = middleware.Rewrite(map[string]string{"/*": "/static/$1"}) 477 | 478 | // e.GET("/*", contentHandler, contentRewrite) 479 | if !tiny { 480 | directory := "static" 481 | if workdirectory != "" { 482 | directory = filepath.Join(workdirectory, "static") 483 | } 484 | logrus.Debug("静态资源目录为:" + directory) 485 | e.Static("/", directory) 486 | } else if tiny { 487 | e.GET("/*", func(c echo.Context) error { 488 | return c.JSON(http.StatusNotFound, map[string]interface{}{"code": 404, "msg": "已开启tiny模式"}) 489 | }) 490 | } 491 | gettoken(dev) 492 | 493 | database.CheckDatabase(databasepath) 494 | c.AddFunc("@every "+strconv.Itoa(flushTokenTime)+"s", func() { gettoken(dev) }) 495 | 496 | if historyEnable { 497 | c.AddFunc("@every "+strconv.Itoa(sampletime)+"s", func() { database.Savetodb(databasepath, dev, tokens, maxsaved) }) 498 | } 499 | c.Start() 500 | 501 | quit := make(chan os.Signal, 1) 502 | signal.Notify(quit, os.Interrupt, syscall.SIGTERM) 503 | 504 | go func() { 505 | <-quit 506 | e.Close() 507 | }() 508 | 509 | e.Start(":" + fmt.Sprint(port)) 510 | } 511 | -------------------------------------------------------------------------------- /modules/config/base.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | . "main/modules/download" 7 | "os" 8 | "path/filepath" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/sirupsen/logrus" 13 | ) 14 | 15 | var ( 16 | password string 17 | key string 18 | ip string 19 | debug bool 20 | port int 21 | tiny bool 22 | routerunit bool 23 | configPath string 24 | workdirectory string 25 | databasepath string 26 | historyEnable bool 27 | dev []Dev 28 | maxsaved int 29 | flushTokenTime int 30 | sampletime int 31 | netdata_routernum int 32 | autocheckupdate string 33 | ) 34 | 35 | type Dev struct { 36 | Password string `json:"password"` 37 | Key string `json:"key"` 38 | IP string `json:"ip"` 39 | RouterUnit bool `json:"routerunit"` 40 | } 41 | type History struct { 42 | Enable bool `json:"enable"` 43 | MaxDeleted int `json:"maxsaved"` 44 | Sampletime int `json:"sampletime"` 45 | } 46 | type Config struct { 47 | Dev []Dev `json:"dev"` 48 | History History `json:"history"` 49 | Debug bool `json:"debug"` 50 | Port int `json:"port"` 51 | Tiny bool `json:"tiny"` 52 | FlushTokenTime int `json:"flushTokenTime"` 53 | Netdata_routernum int `json:"netdata_routernum"` 54 | } 55 | 56 | func GetConfigInfo() (dev []Dev, debug bool, port int, tiny bool, workdirectory string, flushTokenTime int, databasepath string, maxsaved int, historyEnable bool, sampletime int, netdata_routernum int) { 57 | appPath, _ := os.Executable() 58 | 59 | flag.StringVar(&configPath, "config", filepath.Join(filepath.Dir(appPath), "config.json"), "配置文件路径") 60 | flag.StringVar(&workdirectory, "workdirectory", "", "工作目录路径") 61 | flag.StringVar(&databasepath, "databasepath", filepath.Join(filepath.Dir(appPath), "database.db"), "数据库路径") 62 | flag.StringVar(&autocheckupdate, "autocheckupdate", "true", "自动检查更新") 63 | flag.Parse() 64 | 65 | autocheckupdatebool, _ := strconv.ParseBool(autocheckupdate) 66 | 67 | logrus.Info("配置文件路径为:" + configPath) 68 | data, err := os.ReadFile(configPath) 69 | 70 | if err != nil { 71 | logrus.Info("未找到配置文件,正在从程序内部导出") 72 | // 使用你的结构体创建一个默认的配置实例 73 | config := Config{ 74 | Dev: []Dev{ 75 | { 76 | Password: "", 77 | Key: "a2ffa5c9be07488bbb04a3a47d3c5f6a", 78 | IP: "192.168.31.1", 79 | RouterUnit: false, 80 | }, 81 | }, 82 | History: History{ 83 | Enable: false, 84 | MaxDeleted: 3000, 85 | Sampletime: 86400, 86 | }, 87 | Debug: true, 88 | Port: 6789, 89 | Tiny: false, 90 | FlushTokenTime: 1800, 91 | Netdata_routernum: 0, 92 | } 93 | configContent, err := json.MarshalIndent(config, "", " ") 94 | checkErr(err) 95 | err = os.WriteFile(configPath, configContent, 0644) 96 | checkErr(err) 97 | logrus.Info("配置文件导出完成,请修改配置文件") 98 | logrus.Info("5秒后退出程序") 99 | time.Sleep(5 * time.Second) 100 | os.Exit(1) 101 | } 102 | 103 | var config Config 104 | err = json.Unmarshal(data, &config) 105 | if err != nil { 106 | logrus.Info("配置文件存在错误") 107 | } 108 | dev = config.Dev 109 | debug = config.Debug 110 | port = config.Port 111 | tiny = config.Tiny 112 | maxsaved = config.History.MaxDeleted 113 | historyEnable = config.History.Enable 114 | sampletime = config.History.Sampletime 115 | flushTokenTime = config.FlushTokenTime 116 | netdata_routernum = config.Netdata_routernum 117 | // logrus.Info(password) 118 | // logrus.Info(key) 119 | if !tiny { 120 | DownloadStatic(workdirectory, false, autocheckupdatebool) 121 | } 122 | if debug { 123 | logrus.SetLevel(logrus.DebugLevel) 124 | } else { 125 | logrus.SetLevel(logrus.InfoLevel) 126 | } 127 | numDevs := len(dev) 128 | if numDevs == 0 { 129 | logrus.Info("未填写路由器信息,请检查配置文件") 130 | logrus.Info("5秒后退出程序") 131 | time.Sleep(5 * time.Second) 132 | os.Exit(1) 133 | } 134 | return dev, debug, port, tiny, workdirectory, flushTokenTime, databasepath, maxsaved, historyEnable, sampletime, netdata_routernum 135 | } 136 | 137 | func checkErr(err error) { 138 | if err != nil { 139 | panic(err) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /modules/database/base.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "io" 5 | "strconv" 6 | 7 | "encoding/json" 8 | "fmt" 9 | "main/modules/config" 10 | "math" 11 | "net/http" 12 | 13 | "github.com/glebarez/sqlite" 14 | "github.com/sirupsen/logrus" 15 | "gorm.io/gorm" 16 | ) 17 | 18 | // For database 19 | type RouterHistory struct { 20 | gorm.Model 21 | Ip string 22 | RouterNum int 23 | Cpu float64 24 | Cpu_tp int 25 | Mem float64 26 | UpSpeed float64 27 | DownSpeed float64 28 | UpTotal float64 29 | DownTotal float64 30 | DeviceNum int 31 | } 32 | type DevicesHistory struct { 33 | gorm.Model 34 | Mac string 35 | UpSpeed float64 36 | DownSpeed float64 37 | UpTotal float64 38 | DownTotal float64 39 | } 40 | type DeviceInfo struct { 41 | DevName string `json:"devname"` 42 | Download int64 `json:"download,string"` 43 | DownSpeed int `json:"downspeed,string"` 44 | Mac string `json:"mac"` 45 | MaxDownloadSpeed int `json:"maxdownloadspeed,string"` 46 | MaxUploadSpeed int `json:"maxuploadspeed,string"` 47 | Online int `json:"online,string"` 48 | Upload int64 `json:"upload,string"` 49 | UpSpeed int `json:"upspeed,string"` 50 | } 51 | 52 | type Dev struct { 53 | Password string `json:"password"` 54 | Key string `json:"key"` 55 | IP string `json:"ip"` 56 | RouterUnit bool `json:"routerunit"` 57 | } 58 | 59 | // CheckDatabase checks the SQLite database file at the given path. 60 | // 61 | // Parameters: 62 | // - databasePath: a string variable that holds the path to the SQLite database file. 63 | // 64 | // Returns: None. 65 | func CheckDatabase(databasePath string) { 66 | // databasePath is a variable that holds the path to the SQLite database file 67 | db, err := gorm.Open(sqlite.Open(databasePath), &gorm.Config{}) 68 | checkErr(err) 69 | 70 | // Check if the history table exists, if not, create it 71 | err = db.AutoMigrate(&RouterHistory{}) 72 | checkErr(err) 73 | err = db.AutoMigrate(&DevicesHistory{}) 74 | checkErr(err) 75 | // Perform CRUD operations on the history table using db.Create, db.First, db.Update, db.Delete methods 76 | defer func() { 77 | sqlDB, err := db.DB() 78 | checkErr(err) 79 | sqlDB.Close() 80 | }() 81 | } 82 | 83 | // Savetodb saves device statistics to the database. 84 | // 85 | // Parameters: 86 | // - databasePath: the path to the database. 87 | // - dev: an array of device configurations. 88 | // - tokens: a map of token IDs to strings. 89 | // - maxsaved: the maximum number of records to delete. 90 | func Savetodb(databasePath string, dev []config.Dev, tokens map[int]string, maxsaved int) { 91 | db, err := gorm.Open(sqlite.Open(databasePath), &gorm.Config{}) 92 | checkErr(err) 93 | var ( 94 | cpu float64 95 | cpu_tp int 96 | mem float64 97 | upSpeed float64 98 | downSpeed float64 99 | upTotal float64 100 | downTotal float64 101 | deviceNum int 102 | devs []interface{} 103 | mac string 104 | ) 105 | for i, d := range dev { 106 | ip := d.IP 107 | routerNum := i 108 | cpu, cpu_tp, mem, upSpeed, downSpeed, upTotal, downTotal, deviceNum, devs = getRouterStats(i, tokens, ip) 109 | var count int64 110 | db.Model(&RouterHistory{}).Where("router_num = ?", routerNum).Count(&count) 111 | if count >= int64(maxsaved) { 112 | logrus.Debug("删除历史数据") 113 | db.Exec("DELETE FROM histories WHERE router_num = ? AND created_at = (SELECT MIN(created_at) FROM histories WHERE router_num = ? );", routerNum, routerNum) 114 | 115 | } 116 | db.Create(&RouterHistory{ 117 | Ip: ip, 118 | RouterNum: routerNum, 119 | Cpu: cpu, 120 | Cpu_tp: cpu_tp, 121 | Mem: mem, 122 | UpSpeed: upSpeed, 123 | DownSpeed: downSpeed, 124 | UpTotal: upTotal, 125 | DownTotal: downTotal, 126 | DeviceNum: deviceNum, 127 | }) 128 | 129 | for _, dev := range devs { 130 | devMap := dev.(map[string]interface{}) 131 | 132 | data, err := json.Marshal(devMap) 133 | checkErr(err) 134 | 135 | var info DeviceInfo 136 | err = json.Unmarshal(data, &info) 137 | checkErr(err) 138 | mac = info.Mac 139 | upSpeed = float64(info.UpSpeed) 140 | downSpeed = float64(info.DownSpeed) 141 | upTotal = float64(info.Upload) 142 | downTotal = float64(info.Download) 143 | db.Create(&DevicesHistory{ 144 | Mac: mac, 145 | UpSpeed: upSpeed, 146 | DownSpeed: downSpeed, 147 | UpTotal: upTotal, 148 | DownTotal: downTotal, 149 | }) 150 | db.Model(&DevicesHistory{}).Where("mac = ?", routerNum).Count(&count) 151 | if count >= int64(maxsaved) { 152 | logrus.Debug("删除历史数据") 153 | db.Exec("DELETE FROM histories WHERE mac = ? AND created_at = (SELECT MIN(created_at) FROM histories WHERE mac = ? );", mac, mac) 154 | 155 | } 156 | } 157 | } 158 | defer func() { 159 | sqlDB, err := db.DB() 160 | checkErr(err) 161 | sqlDB.Close() 162 | }() 163 | } 164 | 165 | func GetRouterHistory(databasePath string, routernum int, fixupfloat bool) []RouterHistory { 166 | 167 | db, err := gorm.Open(sqlite.Open(databasePath), &gorm.Config{}) 168 | checkErr(err) 169 | var history []RouterHistory 170 | db.Where("router_num = ?", routernum).Find(&history) 171 | // 处理浮点数精度问题 172 | if fixupfloat { 173 | for i := range history { 174 | history[i].Cpu = round(history[i].Cpu, .5, 2) 175 | history[i].Mem = round(history[i].Mem, .5, 2) 176 | history[i].UpSpeed = round(history[i].UpSpeed, .5, 2) 177 | history[i].DownSpeed = round(history[i].DownSpeed, .5, 2) 178 | history[i].UpTotal = round(history[i].UpTotal, .5, 2) 179 | history[i].DownTotal = round(history[i].DownTotal, .5, 2) 180 | 181 | } 182 | 183 | } 184 | defer func() { 185 | sqlDB, err := db.DB() 186 | checkErr(err) 187 | sqlDB.Close() 188 | }() 189 | return history 190 | } 191 | 192 | func GetDeviceHistory(databasePath string, deviceMac string, fixupfloat bool) []DevicesHistory { 193 | 194 | db, err := gorm.Open(sqlite.Open(databasePath), &gorm.Config{}) 195 | checkErr(err) 196 | var history []DevicesHistory 197 | db.Where("mac = ?", deviceMac).Find(&history) 198 | // 处理浮点数精度问题 199 | if fixupfloat { 200 | for i := range history { 201 | history[i].UpSpeed = round(history[i].UpSpeed, .5, 2) 202 | history[i].DownSpeed = round(history[i].DownSpeed, .5, 2) 203 | history[i].UpTotal = round(history[i].UpTotal, .5, 2) 204 | history[i].DownTotal = round(history[i].DownTotal, .5, 2) 205 | } 206 | 207 | } 208 | defer func() { 209 | sqlDB, err := db.DB() 210 | checkErr(err) 211 | sqlDB.Close() 212 | }() 213 | return history 214 | } 215 | 216 | func getRouterStats(routernum int, tokens map[int]string, ip string) (float64, int, float64, float64, float64, float64, float64, int, []interface{}) { 217 | if tokens[routernum] == "" { 218 | return 0, 0, 0, 0, 0, 0, 0, 0, []interface{}{} 219 | } 220 | url := fmt.Sprintf("http://%s/cgi-bin/luci/;stok=%s/api/misystem/status", ip, tokens[routernum]) 221 | resp, err := http.Get(url) 222 | checkErr(err) 223 | defer resp.Body.Close() 224 | body, _ := io.ReadAll(resp.Body) 225 | var result map[string]interface{} 226 | json.Unmarshal(body, &result) 227 | 228 | upspeed, _ := strconv.ParseFloat(result["wan"].(map[string]interface{})["upspeed"].(string), 64) 229 | downspeed, _ := strconv.ParseFloat(result["wan"].(map[string]interface{})["downspeed"].(string), 64) 230 | uploadtotal, _ := strconv.ParseFloat(result["wan"].(map[string]interface{})["upload"].(string), 64) 231 | downloadtotal, _ := strconv.ParseFloat(result["wan"].(map[string]interface{})["download"].(string), 64) 232 | cpuload := result["cpu"].(map[string]interface{})["load"].(float64) * 100 233 | cpu_tp := int(result["temperature"].(float64)) 234 | memusage := result["mem"].(map[string]interface{})["usage"].(float64) * 100 235 | devicenum_now := int(result["count"].(map[string]interface{})["online"].(float64)) 236 | devs := result["dev"].([]interface{}) 237 | 238 | return cpuload, cpu_tp, memusage, upspeed, downspeed, uploadtotal, downloadtotal, devicenum_now, devs 239 | } 240 | 241 | // func roundToOneDecimal(num float64) float64 { 242 | // return math.Round(num*100) / 100 243 | // } 244 | 245 | func checkErr(err error) { 246 | if err != nil { 247 | logrus.Debug(err) 248 | } 249 | } 250 | func round(val float64, roundOn float64, places int) (newVal float64) { 251 | var rounder float64 252 | pow := math.Pow(10, float64(places)) 253 | intermed := val * pow 254 | _, frac := math.Modf(intermed) 255 | 256 | if frac >= roundOn { 257 | rounder = math.Ceil(intermed) 258 | } else { 259 | rounder = math.Floor(intermed) 260 | } 261 | newVal = rounder / pow 262 | return 263 | } 264 | -------------------------------------------------------------------------------- /modules/download/base.go: -------------------------------------------------------------------------------- 1 | package download 2 | 3 | import ( 4 | "archive/zip" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | var ( 17 | Version string 18 | ) 19 | 20 | func DownloadStatic(workdirectory string, force bool, checkupdate bool) error { 21 | directory := "static" 22 | if workdirectory != "" { 23 | directory = filepath.Join(workdirectory, "static") 24 | } 25 | if force { 26 | //删除 27 | os.RemoveAll(directory) 28 | } 29 | 30 | _, err := os.Stat(directory) 31 | if os.IsNotExist(err) || force { 32 | logrus.Info("正从'Mirouterui/static'下载静态资源") 33 | downloadfile(directory) 34 | return nil 35 | } 36 | 37 | // 读取/static/version/index.html 38 | f, err := os.Open(filepath.Join(directory, "version", "index.html")) 39 | if err != nil { 40 | logrus.Info("无法读取静态资源版本号,重新下载") 41 | os.RemoveAll(directory) 42 | downloadfile(directory) 43 | return err 44 | } 45 | 46 | defer f.Close() 47 | forntendVersion, err := io.ReadAll(f) 48 | checkErr(err) 49 | logrus.Info("静态资源已存在,版本号为" + string(forntendVersion)) 50 | 51 | // 检查更新 52 | if checkupdate { 53 | resp, err := http.Get("https://mrui-api.hzchu.top/v2/api/checkupdate") 54 | 55 | 56 | if err != nil { 57 | logrus.Info("无法获取更新信息,跳过检查") 58 | return err 59 | } 60 | 61 | 62 | defer resp.Body.Close() 63 | body, err := io.ReadAll(resp.Body) 64 | checkErr(err) 65 | var result map[string]interface{} 66 | json.Unmarshal(body, &result) 67 | front := result["front"].(map[string]interface{}) 68 | frontversion := front["version"] 69 | frontchangelog := front["changelog"] 70 | 71 | backend := result["backend"].(map[string]interface{}) 72 | backendversion := backend["version"] 73 | backendchangelog := front["changelog"] 74 | 75 | if backendversion != string(Version) { 76 | message := fmt.Sprintf("后端程序发现新版本(%v),请及时更新。更新日志:%v", backendversion, backendchangelog) 77 | logrus.Info(message) 78 | } 79 | 80 | if frontversion != string(forntendVersion) { 81 | message := fmt.Sprintf("前端文件发现新版本(%v),在前端页面中进行更新。更新日志:%v", frontversion, frontchangelog) 82 | logrus.Info(message) 83 | os.RemoveAll(directory) 84 | downloadfile(directory) 85 | } 86 | } else { 87 | logrus.Info("跳过检查更新") 88 | } 89 | return nil 90 | } 91 | func downloadfile(directory string) { 92 | resp, err := http.Get("http://mrui-api.hzchu.top/downloadstatic") 93 | checkErr(err) 94 | defer resp.Body.Close() 95 | 96 | out, err := os.CreateTemp("", "*.zip") 97 | checkErr(err) 98 | defer out.Close() 99 | 100 | _, err = io.Copy(out, resp.Body) 101 | checkErr(err) 102 | 103 | err = unzip(out.Name(), directory) 104 | checkErr(err) 105 | } 106 | 107 | func unzip(src, dest string) error { 108 | r, err := zip.OpenReader(src) 109 | if err != nil { 110 | logrus.Info("静态资源下载失败,请尝试手动下载") 111 | return err 112 | } 113 | defer r.Close() 114 | 115 | for _, f := range r.File { 116 | rc, err := f.Open() 117 | checkErr(err) 118 | fname := f.Name 119 | if len(fname) > 26 { 120 | fname = fname[26:] 121 | } 122 | fpath := filepath.Join(dest, fname) 123 | 124 | if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) { 125 | return fmt.Errorf("%s: illegal file path", fpath) 126 | } 127 | 128 | if f.FileInfo().IsDir() { 129 | os.MkdirAll(fpath, os.ModePerm) 130 | } else { 131 | os.MkdirAll(filepath.Dir(fpath), os.ModePerm) 132 | outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) 133 | if err != nil { 134 | return err 135 | } 136 | _, err = io.Copy(outFile, rc) 137 | checkErr(err) 138 | outFile.Close() 139 | } 140 | 141 | rc.Close() 142 | } 143 | 144 | return nil 145 | } 146 | 147 | func checkErr(err error) { 148 | if err != nil { 149 | logrus.Panic(err) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /modules/login/base.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "crypto/sha1" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "math/rand" 11 | "net/http" 12 | "net/url" 13 | "os" 14 | "time" 15 | 16 | "github.com/sirupsen/logrus" 17 | ) 18 | 19 | var ( 20 | password string 21 | key string 22 | ip string 23 | token string 24 | routername string 25 | hardware string 26 | ) 27 | 28 | func createNonce() string { 29 | typeVar := 0 30 | deviceID := "00:e0:4f:27:3d:09" //无效参数? 31 | timeVar := int(time.Now().Unix()) 32 | randomVar := rand.Intn(10000) 33 | return fmt.Sprintf("%d_%s_%d_%d", typeVar, deviceID, timeVar, randomVar) 34 | } 35 | 36 | func hashPassword(pwd string, nonce string, key string) string { 37 | pwdKey := pwd + key 38 | pwdKeyHash := sha1.New() 39 | pwdKeyHash.Write([]byte(pwdKey)) 40 | pwdKeyHashStr := fmt.Sprintf("%x", pwdKeyHash.Sum(nil)) 41 | 42 | noncePwdKey := nonce + pwdKeyHashStr 43 | noncePwdKeyHash := sha1.New() 44 | noncePwdKeyHash.Write([]byte(noncePwdKey)) 45 | noncePwdKeyHashStr := fmt.Sprintf("%x", noncePwdKeyHash.Sum(nil)) 46 | 47 | return noncePwdKeyHashStr 48 | } 49 | func newhashPassword(pwd string, nonce string, key string) string { 50 | pwdKey := pwd + key 51 | pwdKeyHash := sha256.Sum256([]byte(pwdKey)) 52 | pwdKeyHashStr := hex.EncodeToString(pwdKeyHash[:]) 53 | 54 | noncePwdKey := nonce + pwdKeyHashStr 55 | noncePwdKeyHash := sha256.Sum256([]byte(noncePwdKey)) 56 | noncePwdKeyHashStr := hex.EncodeToString(noncePwdKeyHash[:]) 57 | 58 | return noncePwdKeyHashStr 59 | } 60 | func getrouterinfo(ip string) (bool, string, string) { 61 | 62 | // 发送 GET 请求 63 | ourl := fmt.Sprintf("http://%s/cgi-bin/luci/api/xqsystem/init_info", ip) 64 | response, err := http.Get(ourl) 65 | if err != nil { 66 | return false, "", "" 67 | } 68 | defer response.Body.Close() 69 | // 读取响应内容 70 | body, err := io.ReadAll(response.Body) 71 | if err != nil { 72 | return false, "", "" 73 | } 74 | 75 | // 解析 JSON 76 | var data map[string]interface{} 77 | err = json.Unmarshal(body, &data) 78 | if err != nil { 79 | return false, "", "" 80 | } 81 | //提取routername 82 | routername = data["routername"].(string) 83 | hardware = data["hardware"].(string) 84 | logrus.Debug("路由器型号为:" + hardware) 85 | logrus.Debug("路由器名称为:" + routername) 86 | // 检查 newEncryptMode 87 | newEncryptMode, ok := data["newEncryptMode"].(float64) 88 | if !ok { 89 | logrus.Debug("使用旧加密模式") 90 | return false, routername, hardware 91 | } 92 | 93 | if newEncryptMode != 0 { 94 | logrus.Debug("使用新加密模式") 95 | logrus.Info("当前路由器可能无法正常获取某些数据!") 96 | return true, routername, hardware 97 | } 98 | return false, routername, hardware 99 | } 100 | 101 | func CheckRouterAvailability(ip string) bool { 102 | client := http.Client{ 103 | Timeout: 5 * time.Second, 104 | } 105 | 106 | _, err := client.Get("http://" + ip) 107 | if err != nil { 108 | logrus.Info("路由器" + ip + "不可用,请检查配置或路由器状态") 109 | return false 110 | } 111 | 112 | return true 113 | } 114 | func GetToken(password string, key string, ip string) (string, string, string) { 115 | logrus.Debug("检查路由器可用性...") 116 | if !CheckRouterAvailability(ip) { 117 | return "", "路由器不可用", "" 118 | } 119 | logrus.Debug("获取路由器信息...") 120 | newEncryptMode, routername, hardware := getrouterinfo(ip) 121 | logrus.Info("更新token...") 122 | nonce := createNonce() 123 | var hashedPassword string 124 | 125 | if newEncryptMode { 126 | hashedPassword = newhashPassword(password, nonce, key) 127 | } else { 128 | hashedPassword = hashPassword(password, nonce, key) 129 | } 130 | 131 | ourl := fmt.Sprintf("http://%s/cgi-bin/luci/api/xqsystem/login", ip) 132 | params := url.Values{} 133 | params.Set("username", "admin") 134 | params.Set("password", hashedPassword) 135 | params.Set("logtype", "2") 136 | params.Set("nonce", nonce) 137 | 138 | resp, err := http.PostForm(ourl, params) 139 | if err != nil { 140 | logrus.Info("登录失败,请检查配置或路由器状态") 141 | logrus.Info(err) 142 | time.Sleep(5 * time.Second) 143 | os.Exit(1) 144 | } 145 | defer resp.Body.Close() 146 | body, _ := io.ReadAll(resp.Body) 147 | var result map[string]interface{} 148 | json.Unmarshal(body, &result) 149 | var code int 150 | if result["code"] != nil { 151 | code = int(result["code"].(float64)) 152 | } else { 153 | logrus.Info("路由器登录请求返回值为空!请检查配置") 154 | } 155 | 156 | if code == 0 { 157 | logrus.Debug("当前token为:" + fmt.Sprint(result["token"])) 158 | token = result["token"].(string) 159 | } else { 160 | logrus.Info("登录失败,请检查配置,以下为返回输出:") 161 | logrus.Info(string(body)) 162 | logrus.Info("5秒后退出程序") 163 | time.Sleep(5 * time.Second) 164 | os.Exit(1) 165 | } 166 | return token, routername, hardware 167 | } 168 | -------------------------------------------------------------------------------- /modules/netdata/base.go: -------------------------------------------------------------------------------- 1 | package netdata 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/patrickmn/go-cache" 12 | "github.com/sirupsen/logrus" 13 | ) 14 | 15 | type Data struct { 16 | API int `json:"api"` 17 | ID string `json:"id"` 18 | Name string `json:"name"` 19 | ViewUpdateEvery int `json:"view_update_every"` 20 | UpdateEvery int `json:"update_every"` 21 | FirstEntry int `json:"first_entry"` 22 | LastEntry int `json:"last_entry"` 23 | Before int `json:"before"` 24 | After int `json:"after"` 25 | DimensionNames []string `json:"dimension_names"` 26 | DimensionIDs []string `json:"dimension_ids"` 27 | LatestValues []int `json:"latest_values"` 28 | ViewLatestValues []int `json:"view_latest_values"` 29 | Dimensions int `json:"dimensions"` 30 | Points int `json:"points"` 31 | Format string `json:"format"` 32 | Result Result `json:"result"` 33 | Min int `json:"min"` 34 | Max int `json:"max"` 35 | } 36 | 37 | type CacheData struct { 38 | CpuLoad int 39 | MemAvailable int 40 | MemTotal int 41 | MemUsage int 42 | UpSpeed int 43 | DownSpeed int 44 | Temperature int 45 | Deviceonline int 46 | uploadtotal int 47 | downloadtotal int 48 | } 49 | type Result struct { 50 | Labels []string `json:"labels"` 51 | Data [][]int `json:"data"` 52 | } 53 | type Dimension struct { 54 | Name string `json:"name"` 55 | Value int `json:"value"` 56 | } 57 | 58 | type DataForAllMetrics struct { 59 | Name string `json:"name"` 60 | Family string `json:"family"` 61 | Context string `json:"context"` 62 | Units string `json:"units"` 63 | LastUpdated int `json:"last_updated"` 64 | Dimensions map[string]Dimension `json:"dimensions"` 65 | } 66 | 67 | var c = cache.New(2*time.Second, 4*time.Second) 68 | 69 | func ProcessData(ip string, token string) (int, int, int, int, int, int, int, int, int, int) { 70 | cacheKey := "netdata-cache" 71 | if x, found := c.Get(cacheKey); found { 72 | data := x.(CacheData) 73 | return data.CpuLoad, data.MemAvailable, data.MemTotal, data.MemUsage, data.UpSpeed, data.DownSpeed, data.Temperature, data.Deviceonline, data.uploadtotal, data.downloadtotal 74 | } 75 | url := fmt.Sprintf("http://%s/cgi-bin/luci/;stok=%s/api/misystem/status", ip, token) 76 | resp, err := http.Get(url) 77 | if err != nil { 78 | logrus.Info(err) 79 | return 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 80 | } 81 | defer resp.Body.Close() 82 | body, _ := io.ReadAll(resp.Body) 83 | var result map[string]interface{} 84 | json.Unmarshal(body, &result) 85 | 86 | cpuLoad, _ := strconv.Atoi(fmt.Sprintf("%.0f", result["cpu"].(map[string]interface{})["load"].(float64)*100)) 87 | memUsage, _ := strconv.Atoi(fmt.Sprintf("%.0f", result["mem"].(map[string]interface{})["usage"].(float64)*100)) 88 | memTotal, _ := strconv.Atoi(result["mem"].(map[string]interface{})["total"].(string)[:len(result["mem"].(map[string]interface{})["total"].(string))-2]) 89 | upSpeed, _ := strconv.Atoi(result["wan"].(map[string]interface{})["upspeed"].(string)) 90 | downSpeed, _ := strconv.Atoi(result["wan"].(map[string]interface{})["downspeed"].(string)) 91 | uploadtotal, _ := strconv.Atoi(result["wan"].(map[string]interface{})["upload"].(string)) 92 | downloadtotal, _ := strconv.Atoi(result["wan"].(map[string]interface{})["download"].(string)) 93 | upSpeed = upSpeed / 1024 * 8 94 | downSpeed = downSpeed / 1024 * 8 95 | 96 | temperature, _ := strconv.Atoi(fmt.Sprintf("%.0f", result["temperature"].(float64))) 97 | memAvailable := memTotal * (100 - memUsage) / 100 98 | deviceonline := int(result["count"].(map[string]interface{})["online"].(float64)) 99 | 100 | data := CacheData{ 101 | CpuLoad: cpuLoad, 102 | MemAvailable: memAvailable, 103 | MemTotal: memTotal, 104 | MemUsage: memUsage, 105 | UpSpeed: upSpeed, 106 | DownSpeed: downSpeed, 107 | Temperature: temperature, 108 | Deviceonline: deviceonline, 109 | uploadtotal: uploadtotal, 110 | downloadtotal: downloadtotal, 111 | } 112 | c.Set(cacheKey, data, cache.DefaultExpiration) 113 | return cpuLoad, memAvailable, memTotal, memUsage, upSpeed, downSpeed, temperature, deviceonline, uploadtotal, downloadtotal 114 | } 115 | 116 | func GenerateArray(id string, latestValue int, FirstEntry int, dimensionName string, dimensionID string) Data { 117 | time := int(time.Now().Unix()) 118 | return Data{ 119 | API: 1, 120 | ID: id, 121 | Name: id, 122 | ViewUpdateEvery: 2, 123 | UpdateEvery: 1, 124 | FirstEntry: FirstEntry, 125 | LastEntry: time, 126 | Before: time - 1, 127 | After: time - 2, 128 | DimensionNames: []string{dimensionName}, 129 | DimensionIDs: []string{dimensionID}, 130 | LatestValues: []int{latestValue}, 131 | ViewLatestValues: []int{latestValue}, 132 | Dimensions: 1, 133 | Points: 1, 134 | Format: "json", 135 | Result: Result{ 136 | Labels: []string{"time", dimensionName}, 137 | Data: [][]int{{1703897272, latestValue}}, 138 | }, 139 | Min: latestValue, 140 | Max: latestValue, 141 | } 142 | } 143 | 144 | func GenerateDataForAllMetrics(id string, family string, units string, latestValue int, dimensionName string) DataForAllMetrics { 145 | time := int(time.Now().Unix()) 146 | return DataForAllMetrics{ 147 | Name: id, 148 | Family: family, 149 | Context: "", 150 | Units: units, 151 | LastUpdated: time, 152 | Dimensions: map[string]Dimension{ 153 | dimensionName: { 154 | Name: dimensionName, 155 | Value: latestValue, 156 | }, 157 | }, 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /modules/tp/base.go: -------------------------------------------------------------------------------- 1 | package tp 2 | 3 | import ( 4 | "encoding/json" 5 | "main/modules/config" 6 | "os/exec" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/labstack/echo/v4" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | var ( 15 | hardware string 16 | dev []config.Dev 17 | cpu_cmd *exec.Cmd 18 | w24g_cmd *exec.Cmd 19 | w5g_cmd *exec.Cmd 20 | ) 21 | 22 | // 获取温度 23 | func GetTemperature(c echo.Context, routernum int, hardware string, dev []config.Dev) (bool, int, int, int, int) { 24 | if !dev[routernum].RouterUnit { 25 | return false, -233, -233, -233, -233 26 | } 27 | var cpu_out, w24g_out, w5g_out []byte 28 | var err1, err2, err3 error 29 | var cpu_tp, fanspeed, w24g_tp, w5g_tp string 30 | switch hardware { 31 | case "CR8809": 32 | cpu_cmd = exec.Command("cat", "/sys/class/thermal/thermal_zone0/temp") 33 | w5g_cmd = exec.Command("cat", "/sys/class/ieee80211/phy0/device/net/wifi0/thermal/temp") //不知道是不是 34 | cpu_out, err1 = cpu_cmd.Output() 35 | w5g_out, err3 = w5g_cmd.Output() 36 | cpu_tp = string(cpu_out) 37 | fanspeed = "-233" 38 | w24g_tp = "-233" 39 | w5g_tp = string(w5g_out) 40 | case "RA69": 41 | cpu_cmd = exec.Command("cat", "/sys/class/thermal/thermal_zone0/temp") 42 | w24g_cmd = exec.Command("cat", "/sys/class/ieee80211/phy0/device/net/wifi1/thermal/temp") 43 | w5g_cmd = exec.Command("cat", "/sys/class/ieee80211/phy0/device/net/wifi0/thermal/temp") 44 | cpu_out, err1 = cpu_cmd.Output() 45 | w24g_out, err2 = w24g_cmd.Output() 46 | w5g_out, err3 = w5g_cmd.Output() 47 | 48 | cpu_tp = string(cpu_out) 49 | fanspeed = "-233" 50 | w24g_tp = string(w24g_out) 51 | w5g_tp = string(w5g_out) 52 | case "R1D": 53 | type Ubus_data struct { 54 | Fanspeed string `json:"fanspeed"` 55 | Temperature string `json:"temperature"` 56 | } 57 | cpu_cmd = exec.Command("ubus", "call", "rmonitor", "status") 58 | cpu_out, err1 = cpu_cmd.Output() 59 | var data Ubus_data 60 | err := json.Unmarshal(cpu_out, &data) 61 | if err != nil { 62 | logrus.Error("获取温度失败,报错信息为" + err.Error()) 63 | } 64 | cpu_tp = data.Temperature 65 | fanspeed = data.Fanspeed 66 | w24g_tp = "-233" 67 | w5g_tp = "-233" 68 | default: 69 | return false, -233, -233, -233, -233 70 | } 71 | 72 | if err1 != nil || err2 != nil || err3 != nil { 73 | logrus.Error("获取温度失败,报错信息为" + err1.Error() + err2.Error() + err3.Error()) 74 | } 75 | cpu_tp_int, _ := strconv.Atoi(strings.ReplaceAll(cpu_tp, "\n", "")) 76 | fanspeed_int, _ := strconv.Atoi(strings.ReplaceAll(fanspeed, "\n", "")) 77 | w24g_tp_int, _ := strconv.Atoi(strings.ReplaceAll(w24g_tp, "\n", "")) 78 | w5g_tp_int, _ := strconv.Atoi(strings.ReplaceAll(w5g_tp, "\n", "")) 79 | return true, cpu_tp_int, fanspeed_int, w24g_tp_int, w5g_tp_int 80 | } 81 | -------------------------------------------------------------------------------- /otherfile/downpage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |