first commit
This commit is contained in:
36
.github/workflows/amd64.yml
vendored
Normal file
36
.github/workflows/amd64.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
name: amd64
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check Out Repo
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile-amd64
|
||||||
|
push: true
|
||||||
|
tags: saphoooo/freebox-exporter
|
||||||
|
|
||||||
|
- name: Image digest
|
||||||
|
run: echo ${{ steps.docker_build.outputs.digest }}
|
36
.github/workflows/armv7.yml
vendored
Normal file
36
.github/workflows/armv7.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
name: armv7
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check Out Repo
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile-armv7
|
||||||
|
push: true
|
||||||
|
tags: saphoooo/freebox-exporter:armv7
|
||||||
|
|
||||||
|
- name: Image digest
|
||||||
|
run: echo ${{ steps.docker_build.outputs.digest }}
|
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ master ]
|
||||||
|
schedule:
|
||||||
|
- cron: '44 5 * * 3'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||||
|
# Learn more:
|
||||||
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
**/freebox_exporter
|
19
Dockerfile-armv7
Normal file
19
Dockerfile-armv7
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
FROM golang:1.14
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN set -x && \
|
||||||
|
go get -d -v . && \
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARM=7 GOARCH=arm go build -ldflags "-w -s" -a -installsuffix cgo -o app .
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
LABEL maintainer="stephane.beuret@gmail.com"
|
||||||
|
|
||||||
|
COPY --from=0 app /
|
||||||
|
|
||||||
|
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app"]
|
28
Dockerfile.amd64
Normal file
28
Dockerfile.amd64
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
FROM golang:1.14
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ADD https://github.com/upx/upx/releases/download/v3.95/upx-3.95-amd64_linux.tar.xz /usr/local
|
||||||
|
|
||||||
|
RUN set -x && \
|
||||||
|
apt update && \
|
||||||
|
apt install -y xz-utils && \
|
||||||
|
xz -d -c /usr/local/upx-3.95-amd64_linux.tar.xz | \
|
||||||
|
tar -xOf - upx-3.95-amd64_linux/upx > /bin/upx && \
|
||||||
|
chmod a+x /bin/upx && \
|
||||||
|
go get -d -v . && \
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o app . && \
|
||||||
|
strip --strip-unneeded app && \
|
||||||
|
upx app
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
LABEL maintainer="stephane.beuret@gmail.com"
|
||||||
|
|
||||||
|
COPY --from=0 app /
|
||||||
|
|
||||||
|
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app"]
|
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
91
README.md
Normal file
91
README.md
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# freebox_exporter
|
||||||
|
|
||||||
|
A Prometheus exporter for Freebox stats
|
||||||
|
|
||||||
|
## Cmds
|
||||||
|
|
||||||
|
`freebox_exporter`
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
- `-endpoint`: Freebox API url (default http://mafreebox.freebox.fr)
|
||||||
|
- `-listen`: port for Prometheus metrics (default :10001)
|
||||||
|
- `-debug`: turn on debug mode
|
||||||
|
- `-fiber`: turn off DSL metric for fiber Freebox
|
||||||
|
|
||||||
|
## Preview
|
||||||
|
|
||||||
|
Here's what you can get in Prometheus / Grafana with freebox_exporter:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# How to use it
|
||||||
|
|
||||||
|
## Compiled binary
|
||||||
|
|
||||||
|
If you want to compile the binary, you can refer to [this document](https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63) which explains how to do it, depending on your OS and architecture. Alternatively, you can use `./build.sh`.
|
||||||
|
|
||||||
|
You can also find the compiled binaries for MacOS, Linux (x86_64, arm64 and arm) and Windows in the release tab.
|
||||||
|
|
||||||
|
### Quick start
|
||||||
|
|
||||||
|
```
|
||||||
|
./freebox_exporter
|
||||||
|
```
|
||||||
|
|
||||||
|
### The following parameters are optional and can be overridden:
|
||||||
|
|
||||||
|
- Freebox API endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
./freebox_exporter -endpoint "http://mafreebox.freebox.fr"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Port
|
||||||
|
|
||||||
|
```
|
||||||
|
./freebox_exporter -listen ":10001"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
### Quick start
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -d --name freebox-exporter --restart on-failure -p 10001:10001 \
|
||||||
|
saphoooo/freebox-exporter
|
||||||
|
```
|
||||||
|
|
||||||
|
### The following parameters are optional and can be overridden:
|
||||||
|
|
||||||
|
- Local token
|
||||||
|
|
||||||
|
Volume allows to save the access token outside of the container to reuse authentication upon an update of the container.
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -d --name freebox-exporter --restart on-failure -p 10001:10001 \
|
||||||
|
-e HOME=token -v /path/to/token:/token saphoooo/freebox-exporter
|
||||||
|
```
|
||||||
|
|
||||||
|
- Freebox API endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -d --name freebox-exporter --restart on-failure -p 10001:10001
|
||||||
|
saphoooo/freebox-exporter -endpoint "http://mafreebox.freebox.fr"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Port
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -d --name freebox-exporter --restart on-failure -p 8080:10001 \
|
||||||
|
saphoooo/freebox-exporter
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caution on first run
|
||||||
|
|
||||||
|
If you launch the application for the first time, you must allow it to access the freebox API.
|
||||||
|
- The application must be launched from the local network.
|
||||||
|
- You have to authorize the application from the freebox front panel.
|
||||||
|
- You have to modify the rights of the application to give it "Modification des réglages de la Freebox"
|
||||||
|
|
||||||
|
Source: https://dev.freebox.fr/sdk/os/
|
227
authz.go
Normal file
227
authz.go
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// storeToken stores app_token in ~/.freebox_token
|
||||||
|
func storeToken(token string, authInf *authInfo) error {
|
||||||
|
err := os.Setenv("FREEBOX_TOKEN", token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(authInf.myStore.location); os.IsNotExist(err) {
|
||||||
|
err := ioutil.WriteFile(authInf.myStore.location, []byte(token), 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// retreiveToken gets the token from file and
|
||||||
|
// load it in environment variable
|
||||||
|
func retreiveToken(authInf *authInfo) (string, error) {
|
||||||
|
if _, err := os.Stat(authInf.myStore.location); os.IsNotExist(err) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadFile(authInf.myStore.location)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err = os.Setenv("FREEBOX_TOKEN", string(data))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTrackID is the initial request to freebox API
|
||||||
|
// get app_token and track_id
|
||||||
|
func getTrackID(authInf *authInfo) (*track, error) {
|
||||||
|
req, _ := json.Marshal(authInf.myApp)
|
||||||
|
buf := bytes.NewReader(req)
|
||||||
|
resp, err := http.Post(authInf.myAPI.authz, "application/json", buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
trackID := track{}
|
||||||
|
err = json.Unmarshal(body, &trackID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = storeToken(trackID.Result.AppToken, authInf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &trackID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getGranted waits for user to validate on the freebox front panel
|
||||||
|
// with a timeout of 15 seconds
|
||||||
|
func getGranted(authInf *authInfo) error {
|
||||||
|
trackID, err := getTrackID(authInf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
url := authInf.myAPI.authz + strconv.Itoa(trackID.Result.TrackID)
|
||||||
|
for i := 0; i < 15; i++ {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
granted := grant{}
|
||||||
|
err = json.Unmarshal(body, &granted)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch granted.Result.Status {
|
||||||
|
case "unknown":
|
||||||
|
return errors.New("the app_token is invalid or has been revoked")
|
||||||
|
case "pending":
|
||||||
|
log.Println("the user has not confirmed the authorization request yet")
|
||||||
|
case "timeout":
|
||||||
|
return errors.New("the user did not confirmed the authorization within the given time")
|
||||||
|
case "granted":
|
||||||
|
log.Println("the app_token is valid and can be used to open a session")
|
||||||
|
i = 15
|
||||||
|
case "denied":
|
||||||
|
return errors.New("the user denied the authorization request")
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getChallenge makes sure the app always has a valid challenge
|
||||||
|
func getChallenge(authInf *authInfo) (*challenge, error) {
|
||||||
|
resp, err := http.Get(authInf.myAPI.login)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
challenged := challenge{}
|
||||||
|
err = json.Unmarshal(body, &challenged)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &challenged, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hmacSha1 encodes app_token in hmac-sha1 and stores it in password
|
||||||
|
func hmacSha1(appToken, challenge string) string {
|
||||||
|
hash := hmac.New(sha1.New, []byte(appToken))
|
||||||
|
hash.Write([]byte(challenge))
|
||||||
|
return hex.EncodeToString(hash.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSession gets a session with freeebox API
|
||||||
|
func getSession(authInf *authInfo, passwd string) (*sessionToken, error) {
|
||||||
|
s := session{
|
||||||
|
AppID: authInf.myApp.AppID,
|
||||||
|
Password: passwd,
|
||||||
|
}
|
||||||
|
req, err := json.Marshal(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf := bytes.NewReader(req)
|
||||||
|
resp, err := http.Post(authInf.myAPI.loginSession, "application/json", buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
token := sessionToken{}
|
||||||
|
err = json.Unmarshal(body, &token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getToken gets a valid session_token and asks for user to change
|
||||||
|
// the set of permissions on the API
|
||||||
|
func getToken(authInf *authInfo, xSessionToken *string) (string, error) {
|
||||||
|
if _, err := os.Stat(authInf.myStore.location); os.IsNotExist(err) {
|
||||||
|
err = getGranted(authInf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := authInf.myReader
|
||||||
|
log.Println("check \"Modification des réglages de la Freebox\" and press enter")
|
||||||
|
_, err = reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err := retreiveToken(authInf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := getSessToken(os.Getenv("FREEBOX_TOKEN"), authInf, xSessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
*xSessionToken = token
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSessToken gets a new token session when the old one has expired
|
||||||
|
func getSessToken(token string, authInf *authInfo, xSessionToken *string) (string, error) {
|
||||||
|
challenge, err := getChallenge(authInf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
password := hmacSha1(token, challenge.Result.Challenge)
|
||||||
|
t, err := getSession(authInf, password)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if t.Success == false {
|
||||||
|
return "", errors.New(t.Msg)
|
||||||
|
}
|
||||||
|
*xSessionToken = t.Result.SessionToken
|
||||||
|
return t.Result.SessionToken, nil
|
||||||
|
}
|
413
authz_test.go
Normal file
413
authz_test.go
Normal file
@ -0,0 +1,413 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRetreiveToken(t *testing.T) {
|
||||||
|
ai := &authInfo{
|
||||||
|
myStore: store{location: "/tmp/token"},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := retreiveToken(ai)
|
||||||
|
if err.Error() != "stat /tmp/token: no such file or directory" {
|
||||||
|
t.Error("Expected bla, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ioutil.WriteFile(ai.myStore.location, []byte("IOI"), 0600)
|
||||||
|
defer os.Remove(ai.myStore.location)
|
||||||
|
|
||||||
|
token, err := retreiveToken(ai)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newToken := os.Getenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
if newToken != "IOI" {
|
||||||
|
t.Error("Expected IOI, but got", newToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
if token != "IOI" {
|
||||||
|
t.Error("Expected IOI, but got", newToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStoreToken(t *testing.T) {
|
||||||
|
var token string
|
||||||
|
|
||||||
|
ai := &authInfo{}
|
||||||
|
token = "IOI"
|
||||||
|
err := storeToken(token, ai)
|
||||||
|
if err.Error() != "open : no such file or directory" {
|
||||||
|
t.Error("Expected open : no such file or directory, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ai.myStore.location = "/tmp/token"
|
||||||
|
err = storeToken(token, ai)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(ai.myStore.location)
|
||||||
|
|
||||||
|
token = os.Getenv("FREEBOX_TOKEN")
|
||||||
|
if token != "IOI" {
|
||||||
|
t.Error("Expected IOI, but got", token)
|
||||||
|
}
|
||||||
|
os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(ai.myStore.location)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(data) != "IOI" {
|
||||||
|
t.Error("Expected IOI, but got", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTrackID(t *testing.T) {
|
||||||
|
ai := &authInfo{
|
||||||
|
myStore: store{location: "/tmp/token"},
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
myTrack := track{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myTrack.Result.AppToken = "IOI"
|
||||||
|
myTrack.Result.TrackID = 101
|
||||||
|
result, _ := json.Marshal(myTrack)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
ai.myAPI.authz = ts.URL
|
||||||
|
trackID, err := getTrackID(ai)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(ai.myStore.location)
|
||||||
|
defer os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
if trackID.Result.TrackID != 101 {
|
||||||
|
t.Error("Expected 101, but got", trackID.Result.TrackID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// as getTrackID have no return value
|
||||||
|
// the result of storeToken func is checked instead
|
||||||
|
token := os.Getenv("FREEBOX_TOKEN")
|
||||||
|
if token != "IOI" {
|
||||||
|
t.Error("Expected IOI, but got", token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGranted(t *testing.T) {
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.RequestURI {
|
||||||
|
case "/unknown/":
|
||||||
|
myTrack := track{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myTrack.Result.TrackID = 101
|
||||||
|
result, _ := json.Marshal(myTrack)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/unknown/101":
|
||||||
|
myGrant := grant{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myGrant.Result.Status = "unknown"
|
||||||
|
result, _ := json.Marshal(myGrant)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/timeout/":
|
||||||
|
myTrack := track{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myTrack.Result.TrackID = 101
|
||||||
|
result, _ := json.Marshal(myTrack)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/timeout/101":
|
||||||
|
myGrant := grant{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myGrant.Result.Status = "timeout"
|
||||||
|
result, _ := json.Marshal(myGrant)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/denied/":
|
||||||
|
myTrack := track{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myTrack.Result.TrackID = 101
|
||||||
|
result, _ := json.Marshal(myTrack)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/denied/101":
|
||||||
|
myGrant := grant{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myGrant.Result.Status = "denied"
|
||||||
|
result, _ := json.Marshal(myGrant)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/granted/":
|
||||||
|
myTrack := track{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myTrack.Result.TrackID = 101
|
||||||
|
result, _ := json.Marshal(myTrack)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/granted/101":
|
||||||
|
myGrant := grant{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myGrant.Result.Status = "granted"
|
||||||
|
result, _ := json.Marshal(myGrant)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
default:
|
||||||
|
fmt.Fprintln(w, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
ai := authInfo{}
|
||||||
|
ai.myAPI.authz = ts.URL + "/unknown/"
|
||||||
|
ai.myStore.location = "/tmp/token"
|
||||||
|
|
||||||
|
err := getGranted(&ai)
|
||||||
|
if err.Error() != "the app_token is invalid or has been revoked" {
|
||||||
|
t.Error("Expected the app_token is invalid or has been revoked, but got", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(ai.myStore.location)
|
||||||
|
defer os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
ai.myAPI.authz = ts.URL + "/timeout/"
|
||||||
|
err = getGranted(&ai)
|
||||||
|
if err.Error() != "the user did not confirmed the authorization within the given time" {
|
||||||
|
t.Error("Expected the user did not confirmed the authorization within the given time, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ai.myAPI.authz = ts.URL + "/denied/"
|
||||||
|
err = getGranted(&ai)
|
||||||
|
if err.Error() != "the user denied the authorization request" {
|
||||||
|
t.Error("Expected the user denied the authorization request, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ai.myAPI.authz = ts.URL + "/granted/"
|
||||||
|
err = getGranted(&ai)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetChallenge(t *testing.T) {
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
myChall := &challenge{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myChall.Result.Challenge = "foobar"
|
||||||
|
result, _ := json.Marshal(myChall)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
ai := &authInfo{
|
||||||
|
myAPI: api{
|
||||||
|
login: ts.URL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
challenged, err := getChallenge(ai)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if challenged.Success != true {
|
||||||
|
t.Error("Expected true, but got", challenged.Success)
|
||||||
|
}
|
||||||
|
|
||||||
|
if challenged.Result.Challenge != "foobar" {
|
||||||
|
t.Error("Expected foobar, but got", challenged.Result.Challenge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHmacSha1(t *testing.T) {
|
||||||
|
hmac := hmacSha1("IOI", "foobar")
|
||||||
|
if hmac != "02fb876a39b64eddcfee3eaa69465cb3e8d53cde" {
|
||||||
|
t.Error("Expected 02fb876a39b64eddcfee3eaa69465cb3e8d53cde, but got", hmac)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSession(t *testing.T) {
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
myToken := &sessionToken{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myToken.Result.Challenge = "foobar"
|
||||||
|
result, _ := json.Marshal(myToken)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
ai := &authInfo{
|
||||||
|
myAPI: api{
|
||||||
|
loginSession: ts.URL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := getSession(ai, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
defer os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
if token.Success != true {
|
||||||
|
t.Error("Expected true, but got", token.Success)
|
||||||
|
}
|
||||||
|
|
||||||
|
if token.Result.Challenge != "foobar" {
|
||||||
|
t.Error("Expected foobar, but got", token.Result.Challenge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetToken(t *testing.T) {
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.RequestURI {
|
||||||
|
case "/login":
|
||||||
|
myChall := &challenge{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myChall.Result.Challenge = "foobar"
|
||||||
|
result, _ := json.Marshal(myChall)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/session":
|
||||||
|
myToken := sessionToken{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myToken.Result.SessionToken = "foobar"
|
||||||
|
result, _ := json.Marshal(myToken)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/granted/":
|
||||||
|
myTrack := track{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myTrack.Result.TrackID = 101
|
||||||
|
result, _ := json.Marshal(myTrack)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/granted/101":
|
||||||
|
myGrant := grant{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myGrant.Result.Status = "granted"
|
||||||
|
result, _ := json.Marshal(myGrant)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
default:
|
||||||
|
fmt.Fprintln(w, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
ai := authInfo{}
|
||||||
|
ai.myStore.location = "/tmp/token"
|
||||||
|
ai.myAPI.login = ts.URL + "/login"
|
||||||
|
ai.myAPI.loginSession = ts.URL + "/session"
|
||||||
|
ai.myAPI.authz = ts.URL + "/granted/"
|
||||||
|
ai.myReader = bufio.NewReader(strings.NewReader("\n"))
|
||||||
|
|
||||||
|
var mySessionToken string
|
||||||
|
|
||||||
|
// the first pass valide getToken without a token stored in a file
|
||||||
|
tk, err := getToken(&ai, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(ai.myStore.location)
|
||||||
|
defer os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
if mySessionToken != "foobar" {
|
||||||
|
t.Error("Expected foobar, but got", mySessionToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tk != "foobar" {
|
||||||
|
t.Error("Expected foobar, but got", tk)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the second pass validate getToken with a token stored in a file:
|
||||||
|
// the first pass creates a file at ai.myStore.location
|
||||||
|
tk, err = getToken(&ai, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mySessionToken != "foobar" {
|
||||||
|
t.Error("Expected foobar, but got", mySessionToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tk != "foobar" {
|
||||||
|
t.Error("Expected foobar, but got", tk)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSessToken(t *testing.T) {
|
||||||
|
|
||||||
|
myToken := &sessionToken{}
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.RequestURI {
|
||||||
|
case "/login":
|
||||||
|
myChall := &challenge{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myChall.Result.Challenge = "foobar"
|
||||||
|
result, _ := json.Marshal(myChall)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/session":
|
||||||
|
myToken.Success = true
|
||||||
|
myToken.Result.SessionToken = "foobar"
|
||||||
|
result, _ := json.Marshal(myToken)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/session2":
|
||||||
|
myToken.Msg = "failed to get a session"
|
||||||
|
myToken.Success = false
|
||||||
|
result, _ := json.Marshal(myToken)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
default:
|
||||||
|
fmt.Fprintln(w, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
ai := authInfo{}
|
||||||
|
ai.myAPI.login = ts.URL + "/login"
|
||||||
|
ai.myAPI.loginSession = ts.URL + "/session"
|
||||||
|
var mySessionToken string
|
||||||
|
|
||||||
|
st, err := getSessToken("token", &ai, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if st != "foobar" {
|
||||||
|
t.Error("Expected foobar, but got", st)
|
||||||
|
}
|
||||||
|
|
||||||
|
ai.myAPI.loginSession = ts.URL + "/session2"
|
||||||
|
|
||||||
|
_, err = getSessToken("token", &ai, &mySessionToken)
|
||||||
|
if err.Error() != "failed to get a session" {
|
||||||
|
t.Error("Expected but got failed to get a session, but got", err)
|
||||||
|
}
|
||||||
|
}
|
8
build.sh
Executable file
8
build.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ARCH=${1:-arm}
|
||||||
|
GOARCH=$ARCH go build -ldflags "-s -w"
|
||||||
|
ls -lh freebox_exporter
|
||||||
|
file freebox_exporter
|
59
changelog.md
Normal file
59
changelog.md
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [1.3] - 2020-10-04
|
||||||
|
|
||||||
|
- Add VPN server metrics, mainly tx and rx for a user on a vpn with scr and local ip as labels
|
||||||
|
|
||||||
|
## [1.2.2] - 2020-10-03
|
||||||
|
|
||||||
|
- Change variable type int to int64 for RRD metrics, causing an error "constant overflow" on arm chipset
|
||||||
|
|
||||||
|
## [1.2] - 2020-05-27
|
||||||
|
|
||||||
|
- Add build script
|
||||||
|
- Log Freebox Server uptime and firmware version
|
||||||
|
- Log G.INP data
|
||||||
|
- Log connection status, protocol and modulation
|
||||||
|
- Log XDSL stats
|
||||||
|
- Don't log incorrect values (previously logged as zero)
|
||||||
|
- Remove dead code
|
||||||
|
|
||||||
|
## [1.1.9] - 2020-04-24
|
||||||
|
|
||||||
|
- Log freeplug speeds and connectivity
|
||||||
|
- Go 1.14
|
||||||
|
- Remove Godeps and vendored files
|
||||||
|
|
||||||
|
## [1.1.7] - 2019-09-04
|
||||||
|
|
||||||
|
- Go 1.13
|
||||||
|
|
||||||
|
## [1.1.6] - 2019-09-04
|
||||||
|
|
||||||
|
- There is no more uncomfortable error message when the application renews its token
|
||||||
|
- Adding a `-fiber` flag so that Freebox fiber users do not capture DSL metrics, which are empty on this type of Freebox
|
||||||
|
|
||||||
|
## [1.1.4] - 2019-09-03
|
||||||
|
|
||||||
|
- Adding a `-debug` flag to have more verbose error logs
|
||||||
|
|
||||||
|
## [1.1.2] - 2019-08-25
|
||||||
|
|
||||||
|
- Improve error messages
|
||||||
|
|
||||||
|
## [1.1.1] - 2019-07-31
|
||||||
|
|
||||||
|
- New Dockerfile for amd64 arch: reduce the image size to 3mb
|
||||||
|
|
||||||
|
## [1.1.0] - 2019-07-31
|
||||||
|
|
||||||
|
- Fix temp metrics
|
||||||
|
- Add Godeps
|
||||||
|
|
||||||
|
## [1.0.1] - 2019-07-30
|
||||||
|
|
||||||
|
- Change error catching
|
||||||
|
|
||||||
|
## [1.0.0] - 2019-07-29
|
||||||
|
|
||||||
|
- Rewriting the application by adding a ton of unit tests
|
9
contrib/README.md
Normal file
9
contrib/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Here you'll find related contributions to the project, such as ready-to-use Grafana dashboards (this will save you from having to reinvent the wheel when using the Freebox Exporter).
|
||||||
|
|
||||||
|
Thanks to [mcanevet](https://gist.github.com/mcanevet) for his contribution:
|
||||||
|
|
||||||
|
<img width="600" alt="mcanevet Grafana dashbord" src="https://user-images.githubusercontent.com/13923756/57589143-de912180-751f-11e9-8bad-7842b35776d9.png">
|
||||||
|
|
||||||
|
Thanks to [Pichon](https://github.com/lepichon) for his contribution:
|
||||||
|
|
||||||
|
<img width="600" alt="lepichon Grafana dashbord" src="https://user-images.githubusercontent.com/13923756/74086200-e9fa8480-4a80-11ea-92d4-a4aca6f8c159.png">
|
544
contrib/lepichon_freebox_grafana_dashboard.json
Normal file
544
contrib/lepichon_freebox_grafana_dashboard.json
Normal file
@ -0,0 +1,544 @@
|
|||||||
|
{
|
||||||
|
"__inputs": [
|
||||||
|
{
|
||||||
|
"name": "DS_PROMETHEUS",
|
||||||
|
"label": "Prometheus",
|
||||||
|
"description": "",
|
||||||
|
"type": "datasource",
|
||||||
|
"pluginId": "prometheus",
|
||||||
|
"pluginName": "Prometheus"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"__requires": [
|
||||||
|
{
|
||||||
|
"type": "grafana",
|
||||||
|
"id": "grafana",
|
||||||
|
"name": "Grafana",
|
||||||
|
"version": "6.6.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "panel",
|
||||||
|
"id": "graph",
|
||||||
|
"name": "Graph",
|
||||||
|
"version": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "datasource",
|
||||||
|
"id": "prometheus",
|
||||||
|
"name": "Prometheus",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "panel",
|
||||||
|
"id": "stat",
|
||||||
|
"name": "Stat",
|
||||||
|
"version": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": "-- Grafana --",
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"gnetId": null,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"cacheTimeout": null,
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"gridPos": {
|
||||||
|
"h": 3,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 11,
|
||||||
|
"links": [],
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"fieldOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"defaults": {
|
||||||
|
"mappings": [],
|
||||||
|
"max": 1,
|
||||||
|
"min": 1,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "rgb(33, 33, 33)",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [],
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "vertical"
|
||||||
|
},
|
||||||
|
"pluginVersion": "6.6.0",
|
||||||
|
"repeat": null,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "freebox_lan_reachable == 1",
|
||||||
|
"instant": true,
|
||||||
|
"legendFormat": "{{name}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Host on LAN",
|
||||||
|
"transparent": true,
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"fill": 1,
|
||||||
|
"fillGradient": 0,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 3
|
||||||
|
},
|
||||||
|
"hiddenSeries": false,
|
||||||
|
"id": 2,
|
||||||
|
"legend": {
|
||||||
|
"alignAsTable": true,
|
||||||
|
"avg": true,
|
||||||
|
"current": true,
|
||||||
|
"hideEmpty": false,
|
||||||
|
"max": true,
|
||||||
|
"min": true,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": true
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 2,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "connected",
|
||||||
|
"options": {
|
||||||
|
"dataLinks": []
|
||||||
|
},
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [
|
||||||
|
{
|
||||||
|
"alias": "/.*Trans.*/",
|
||||||
|
"transform": "negative-Y"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "irate(freebox_net_down_bytes[5m])",
|
||||||
|
"format": "time_series",
|
||||||
|
"intervalFactor": 2,
|
||||||
|
"legendFormat": "Download Usage",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "freebox_net_bw_down_bytes / 10",
|
||||||
|
"legendFormat": "Download Bandwidth",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Network Traffic Download (bytes/sec)",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"decimals": null,
|
||||||
|
"format": "Bps",
|
||||||
|
"label": "Bytes out (-) / in (+)",
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": "0",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"fill": 1,
|
||||||
|
"fillGradient": 0,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 11
|
||||||
|
},
|
||||||
|
"hiddenSeries": false,
|
||||||
|
"id": 13,
|
||||||
|
"legend": {
|
||||||
|
"alignAsTable": true,
|
||||||
|
"avg": true,
|
||||||
|
"current": true,
|
||||||
|
"max": true,
|
||||||
|
"min": true,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": true
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 2,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"options": {
|
||||||
|
"dataLinks": []
|
||||||
|
},
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [
|
||||||
|
{
|
||||||
|
"alias": "/.*Trans.*/",
|
||||||
|
"transform": "negative-Y"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "irate(freebox_net_up_bytes[5m])",
|
||||||
|
"format": "time_series",
|
||||||
|
"intervalFactor": 2,
|
||||||
|
"legendFormat": "Upload Usage",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "freebox_net_bw_up_bytes / 10",
|
||||||
|
"legendFormat": "Upload Bandwidth",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Network Traffic Upload (bytes/sec)",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"decimals": null,
|
||||||
|
"format": "Bps",
|
||||||
|
"label": "Bytes out (-) / in (+)",
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": "0",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"description": "",
|
||||||
|
"fill": 1,
|
||||||
|
"fillGradient": 0,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 11,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 19
|
||||||
|
},
|
||||||
|
"hiddenSeries": false,
|
||||||
|
"id": 9,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"options": {
|
||||||
|
"dataLinks": []
|
||||||
|
},
|
||||||
|
"percentage": false,
|
||||||
|
"pluginVersion": "6.6.0",
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "freebox_system_fan_rpm",
|
||||||
|
"format": "time_series",
|
||||||
|
"intervalFactor": 1,
|
||||||
|
"legendFormat": "{{name}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Fan Speed",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"decimals": 3,
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "${DS_PROMETHEUS}",
|
||||||
|
"fill": 1,
|
||||||
|
"fillGradient": 0,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 11,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 19
|
||||||
|
},
|
||||||
|
"hiddenSeries": false,
|
||||||
|
"id": 6,
|
||||||
|
"legend": {
|
||||||
|
"alignAsTable": true,
|
||||||
|
"avg": true,
|
||||||
|
"current": true,
|
||||||
|
"max": true,
|
||||||
|
"min": true,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": true
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"options": {
|
||||||
|
"dataLinks": []
|
||||||
|
},
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "freebox_system_temp_celsius",
|
||||||
|
"format": "time_series",
|
||||||
|
"intervalFactor": 1,
|
||||||
|
"legendFormat": "{{name}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "System Temperature",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "celsius",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "5s",
|
||||||
|
"schemaVersion": 22,
|
||||||
|
"style": "dark",
|
||||||
|
"tags": [
|
||||||
|
"prometheus",
|
||||||
|
"freebox"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {
|
||||||
|
"refresh_intervals": [
|
||||||
|
"5s",
|
||||||
|
"10s",
|
||||||
|
"30s",
|
||||||
|
"1m",
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"30m",
|
||||||
|
"1h",
|
||||||
|
"2h",
|
||||||
|
"1d"
|
||||||
|
],
|
||||||
|
"time_options": [
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"1h",
|
||||||
|
"6h",
|
||||||
|
"12h",
|
||||||
|
"24h",
|
||||||
|
"2d",
|
||||||
|
"7d",
|
||||||
|
"30d"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "Freebox",
|
||||||
|
"uid": "TVsfEYmZk",
|
||||||
|
"version": 14
|
||||||
|
}
|
418
contrib/mcanevet_freebox_grafana_dashboard.json
Normal file
418
contrib/mcanevet_freebox_grafana_dashboard.json
Normal file
@ -0,0 +1,418 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": "-- Grafana --",
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"gnetId": null,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": 5,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "Prometheus",
|
||||||
|
"fill": 2,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"legend": {
|
||||||
|
"alignAsTable": true,
|
||||||
|
"avg": true,
|
||||||
|
"current": true,
|
||||||
|
"max": true,
|
||||||
|
"min": true,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": true
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [
|
||||||
|
{
|
||||||
|
"alias": "/.*Trans.*/",
|
||||||
|
"transform": "negative-Y"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "irate(freebox_net_down_bytes[5m])",
|
||||||
|
"format": "time_series",
|
||||||
|
"intervalFactor": 2,
|
||||||
|
"legendFormat": "Receive",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "irate(freebox_net_up_bytes[5m])",
|
||||||
|
"format": "time_series",
|
||||||
|
"intervalFactor": 2,
|
||||||
|
"legendFormat": "Transmit",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Network Traffic by bytes",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"decimals": null,
|
||||||
|
"format": "Bps",
|
||||||
|
"label": "Bytes out (-) / in (+)",
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"fill": 1,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"legend": {
|
||||||
|
"alignAsTable": true,
|
||||||
|
"avg": true,
|
||||||
|
"current": true,
|
||||||
|
"max": true,
|
||||||
|
"min": true,
|
||||||
|
"rightSide": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": true
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "freebox_system_fan_rpm",
|
||||||
|
"format": "time_series",
|
||||||
|
"intervalFactor": 1,
|
||||||
|
"legendFormat": "{{name}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Fan RPM",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"decimals": null,
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": "0",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"fill": 1,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 8
|
||||||
|
},
|
||||||
|
"id": 6,
|
||||||
|
"legend": {
|
||||||
|
"alignAsTable": true,
|
||||||
|
"avg": true,
|
||||||
|
"current": true,
|
||||||
|
"max": true,
|
||||||
|
"min": true,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": true
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "freebox_system_temp_celsius",
|
||||||
|
"format": "time_series",
|
||||||
|
"intervalFactor": 1,
|
||||||
|
"legendFormat": "{{name}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "System Temperature",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "celsius",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cacheTimeout": null,
|
||||||
|
"colorBackground": false,
|
||||||
|
"colorValue": false,
|
||||||
|
"colors": [
|
||||||
|
"#299c46",
|
||||||
|
"rgba(237, 129, 40, 0.89)",
|
||||||
|
"#d44a3a"
|
||||||
|
],
|
||||||
|
"format": "none",
|
||||||
|
"gauge": {
|
||||||
|
"maxValue": 100,
|
||||||
|
"minValue": 0,
|
||||||
|
"show": false,
|
||||||
|
"thresholdLabels": false,
|
||||||
|
"thresholdMarkers": true
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 8
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"interval": null,
|
||||||
|
"links": [],
|
||||||
|
"mappingType": 1,
|
||||||
|
"mappingTypes": [
|
||||||
|
{
|
||||||
|
"name": "value to text",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "range to text",
|
||||||
|
"value": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"maxDataPoints": 100,
|
||||||
|
"nullPointMode": "connected",
|
||||||
|
"nullText": null,
|
||||||
|
"postfix": "",
|
||||||
|
"postfixFontSize": "50%",
|
||||||
|
"prefix": "",
|
||||||
|
"prefixFontSize": "50%",
|
||||||
|
"rangeMaps": [
|
||||||
|
{
|
||||||
|
"from": "null",
|
||||||
|
"text": "N/A",
|
||||||
|
"to": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sparkline": {
|
||||||
|
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||||
|
"full": false,
|
||||||
|
"lineColor": "rgb(31, 120, 193)",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
"tableColumn": "",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "sum(freebox_lan_reachable)",
|
||||||
|
"format": "time_series",
|
||||||
|
"intervalFactor": 1,
|
||||||
|
"legendFormat": "",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": "",
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Hosts on LAN",
|
||||||
|
"type": "singlestat",
|
||||||
|
"valueFontSize": "80%",
|
||||||
|
"valueMaps": [
|
||||||
|
{
|
||||||
|
"op": "=",
|
||||||
|
"text": "N/A",
|
||||||
|
"value": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"valueName": "current"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "30s",
|
||||||
|
"schemaVersion": 18,
|
||||||
|
"style": "dark",
|
||||||
|
"tags": [
|
||||||
|
"prometheus",
|
||||||
|
"freebox"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {
|
||||||
|
"refresh_intervals": [
|
||||||
|
"5s",
|
||||||
|
"10s",
|
||||||
|
"30s",
|
||||||
|
"1m",
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"30m",
|
||||||
|
"1h",
|
||||||
|
"2h",
|
||||||
|
"1d"
|
||||||
|
],
|
||||||
|
"time_options": [
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"1h",
|
||||||
|
"6h",
|
||||||
|
"12h",
|
||||||
|
"24h",
|
||||||
|
"2d",
|
||||||
|
"7d",
|
||||||
|
"30d"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "Freebox",
|
||||||
|
"uid": "TVsfEYmZk",
|
||||||
|
"version": 14
|
||||||
|
}
|
255
gauges.go
Normal file
255
gauges.go
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// XXX: see https://dev.freebox.fr/sdk/os/ for API documentation
|
||||||
|
// XXX: see https://prometheus.io/docs/practices/naming/ for metric names
|
||||||
|
|
||||||
|
// connectionXdsl
|
||||||
|
connectionXdslStatusUptimeGauges = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_connection_xdsl_status_uptime_seconds_total",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"status",
|
||||||
|
"protocol",
|
||||||
|
"modulation",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
connectionXdslDownAttnGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_connection_xdsl_down_attn_decibels",
|
||||||
|
})
|
||||||
|
connectionXdslUpAttnGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_connection_xdsl_up_attn_decibels",
|
||||||
|
})
|
||||||
|
connectionXdslDownSnrGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_connection_xdsl_down_snr_decibels",
|
||||||
|
})
|
||||||
|
connectionXdslUpSnrGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_connection_xdsl_up_snr_decibels",
|
||||||
|
})
|
||||||
|
|
||||||
|
connectionXdslErrorGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_connection_xdsl_errors_total",
|
||||||
|
Help: "Error counts",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"direction", // up|down
|
||||||
|
"name", // crc|es|fec|hec
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
connectionXdslGinpGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_connection_xdsl_ginp",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"direction", // up|down
|
||||||
|
"name", // enabled|rtx_(tx|c|uc)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
connectionXdslNitroGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_connection_xdsl_nitro",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"direction", // up|down
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// RRD dsl [unstable]
|
||||||
|
rateUpGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_dsl_up_bytes",
|
||||||
|
Help: "Available upload bandwidth (in byte/s)",
|
||||||
|
})
|
||||||
|
rateDownGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_dsl_down_bytes",
|
||||||
|
Help: "Available download bandwidth (in byte/s)",
|
||||||
|
})
|
||||||
|
snrUpGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_dsl_snr_up_decibel",
|
||||||
|
Help: "Upload signal/noise ratio (in 1/10 dB)",
|
||||||
|
})
|
||||||
|
snrDownGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_dsl_snr_down_decibel",
|
||||||
|
Help: "Download signal/noise ratio (in 1/10 dB)",
|
||||||
|
})
|
||||||
|
|
||||||
|
// freeplug
|
||||||
|
freeplugRxRateGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_freeplug_rx_rate_bits",
|
||||||
|
Help: "rx rate (from the freeplugs to the \"cco\" freeplug) (in bits/s) -1 if not available",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"id",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
freeplugTxRateGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_freeplug_tx_rate_bits",
|
||||||
|
Help: "tx rate (from the \"cco\" freeplug to the freeplugs) (in bits/s) -1 if not available",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"id",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
freeplugHasNetworkGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_freeplug_has_network",
|
||||||
|
Help: "is connected to the network",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"id",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// RRD Net [unstable]
|
||||||
|
bwUpGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_net_bw_up_bytes",
|
||||||
|
Help: "Upload available bandwidth (in byte/s)",
|
||||||
|
})
|
||||||
|
bwDownGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_net_bw_down_bytes",
|
||||||
|
Help: "Download available bandwidth (in byte/s)",
|
||||||
|
})
|
||||||
|
netRateUpGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_net_up_bytes",
|
||||||
|
Help: "Upload rate (in byte/s)",
|
||||||
|
})
|
||||||
|
netRateDownGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_net_down_bytes",
|
||||||
|
Help: "Download rate (in byte/s)",
|
||||||
|
})
|
||||||
|
vpnRateUpGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_net_vpn_up_bytes",
|
||||||
|
Help: "Vpn client upload rate (in byte/s)",
|
||||||
|
})
|
||||||
|
vpnRateDownGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_net_vpn_down_bytes",
|
||||||
|
Help: "Vpn client download rate (in byte/s)",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Lan
|
||||||
|
lanReachableGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_lan_reachable",
|
||||||
|
Help: "Hosts reachable on LAN",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"name", // hostname
|
||||||
|
"vendor",
|
||||||
|
"ip",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
systemTempGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_system_temp_celsius",
|
||||||
|
Help: "Temperature sensors reported by system (in °C)",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"name",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
systemFanGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_system_fan_rpm",
|
||||||
|
Help: "Fan speed reported by system (in RPM)",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"name",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
systemUptimeGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_system_uptime_seconds_total",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"firmware_version",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// wifi
|
||||||
|
wifiLabels = []string{
|
||||||
|
"access_point",
|
||||||
|
"hostname",
|
||||||
|
"state",
|
||||||
|
}
|
||||||
|
|
||||||
|
wifiSignalGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_wifi_signal_attenuation_db",
|
||||||
|
Help: "Wifi signal attenuation in decibel",
|
||||||
|
},
|
||||||
|
wifiLabels,
|
||||||
|
)
|
||||||
|
|
||||||
|
wifiInactiveGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_wifi_inactive_duration_seconds",
|
||||||
|
Help: "Wifi inactive duration in seconds",
|
||||||
|
},
|
||||||
|
wifiLabels,
|
||||||
|
)
|
||||||
|
|
||||||
|
wifiConnectionDurationGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_wifi_connection_duration_seconds",
|
||||||
|
Help: "Wifi connection duration in seconds",
|
||||||
|
},
|
||||||
|
wifiLabels,
|
||||||
|
)
|
||||||
|
|
||||||
|
wifiRXBytesGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_wifi_rx_bytes",
|
||||||
|
Help: "Wifi received data (from station to Freebox) in bytes",
|
||||||
|
},
|
||||||
|
wifiLabels,
|
||||||
|
)
|
||||||
|
|
||||||
|
wifiTXBytesGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_wifi_tx_bytes",
|
||||||
|
Help: "Wifi transmitted data (from Freebox to station) in bytes",
|
||||||
|
},
|
||||||
|
wifiLabels,
|
||||||
|
)
|
||||||
|
|
||||||
|
wifiRXRateGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_wifi_rx_rate",
|
||||||
|
Help: "Wifi reception data rate (from station to Freebox) in bytes/seconds",
|
||||||
|
},
|
||||||
|
wifiLabels,
|
||||||
|
)
|
||||||
|
|
||||||
|
wifiTXRateGauges = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "freebox_wifi_tx_rate",
|
||||||
|
Help: "Wifi transmission data rate (from Freebox to station) in bytes/seconds",
|
||||||
|
},
|
||||||
|
wifiLabels,
|
||||||
|
)
|
||||||
|
|
||||||
|
// vpn server connections list [unstable]
|
||||||
|
vpnServerConnectionsList = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "vpn_server_connections_list",
|
||||||
|
Help: "VPN server connections list",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"user",
|
||||||
|
"vpn",
|
||||||
|
"src_ip",
|
||||||
|
"local_ip",
|
||||||
|
"name", // rx_bytes|tx_bytes
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
573
getters.go
Normal file
573
getters.go
Normal file
@ -0,0 +1,573 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
apiErrors = map[string]error{
|
||||||
|
"invalid_token": errors.New("The app token you are trying to use is invalid or has been revoked"),
|
||||||
|
"insufficient_rights": errors.New("Your app permissions does not allow accessing this API"),
|
||||||
|
"denied_from_external_ip": errors.New("You are trying to get an app_token from a remote IP"),
|
||||||
|
"invalid_request": errors.New("Your request is invalid"),
|
||||||
|
"ratelimited": errors.New("Too many auth error have been made from your IP"),
|
||||||
|
"new_apps_denied": errors.New("New application token request has been disabled"),
|
||||||
|
"apps_denied": errors.New("API access from apps has been disabled"),
|
||||||
|
"internal_error": errors.New("Internal error"),
|
||||||
|
"db_error": errors.New("Oops, the database you are trying to access doesn't seem to exist"),
|
||||||
|
"nodev": errors.New("Invalid interface"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *rrd) status() error {
|
||||||
|
if apiErrors[r.ErrorCode] == nil {
|
||||||
|
return errors.New("RRD: The API returns an unknown error_code: " + r.ErrorCode)
|
||||||
|
}
|
||||||
|
return apiErrors[r.ErrorCode]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lan) status() error {
|
||||||
|
if apiErrors[l.ErrorCode] == nil {
|
||||||
|
return errors.New("LAN: The API returns an unknown error_code: " + l.ErrorCode)
|
||||||
|
}
|
||||||
|
return apiErrors[l.ErrorCode]
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFreeboxToken(authInf *authInfo, xSessionToken *string) (string, error) {
|
||||||
|
token := os.Getenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
if token == "" {
|
||||||
|
var err error
|
||||||
|
*xSessionToken, err = getToken(authInf, xSessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
token = *xSessionToken
|
||||||
|
}
|
||||||
|
|
||||||
|
if *xSessionToken == "" {
|
||||||
|
var err error
|
||||||
|
*xSessionToken, err = getSessToken(token, authInf, xSessionToken)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
token = *xSessionToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPostRequest() *postRequest {
|
||||||
|
return &postRequest{
|
||||||
|
method: "POST",
|
||||||
|
url: mafreebox + "api/v4/rrd/",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConnectionXdsl(authInf *authInfo, pr *postRequest, xSessionToken *string) (connectionXdsl, error) {
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest(pr.method, pr.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return connectionXdsl{}, err
|
||||||
|
}
|
||||||
|
req.Header.Add(pr.header, *xSessionToken)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return connectionXdsl{}, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 404 {
|
||||||
|
return connectionXdsl{}, errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return connectionXdsl{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionXdslResp := connectionXdsl{}
|
||||||
|
err = json.Unmarshal(body, &connectionXdslResp)
|
||||||
|
if err != nil {
|
||||||
|
if debug {
|
||||||
|
log.Println(string(body))
|
||||||
|
}
|
||||||
|
return connectionXdsl{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectionXdslResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDsl(authInf *authInfo, pr *postRequest, xSessionToken *string) ([]int64, error) {
|
||||||
|
d := &database{
|
||||||
|
DB: "dsl",
|
||||||
|
Fields: []string{"rate_up", "rate_down", "snr_up", "snr_down"},
|
||||||
|
Precision: 10,
|
||||||
|
DateStart: int(time.Now().Unix() - 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
freeboxToken, err := setFreeboxToken(authInf, xSessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
client := http.Client{}
|
||||||
|
r, err := json.Marshal(*d)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
buf := bytes.NewReader(r)
|
||||||
|
req, err := http.NewRequest(pr.method, pr.url, buf)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
req.Header.Add(pr.header, *xSessionToken)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 404 {
|
||||||
|
return []int64{}, errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
rrdTest := rrd{}
|
||||||
|
err = json.Unmarshal(body, &rrdTest)
|
||||||
|
if err != nil {
|
||||||
|
if debug {
|
||||||
|
log.Println(string(body))
|
||||||
|
}
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rrdTest.ErrorCode == "auth_required" {
|
||||||
|
*xSessionToken, err = getSessToken(freeboxToken, authInf, xSessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rrdTest.ErrorCode != "" && rrdTest.ErrorCode != "auth_required" {
|
||||||
|
if rrdTest.status().Error() == "Unknown return code from the API" {
|
||||||
|
fmt.Println("getDsl")
|
||||||
|
}
|
||||||
|
return []int64{}, rrdTest.status()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rrdTest.Result.Data) == 0 {
|
||||||
|
return []int64{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := []int64{rrdTest.Result.Data[0]["rate_up"], rrdTest.Result.Data[0]["rate_down"], rrdTest.Result.Data[0]["snr_up"], rrdTest.Result.Data[0]["snr_down"]}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTemp(authInf *authInfo, pr *postRequest, xSessionToken *string) ([]int64, error) {
|
||||||
|
d := &database{
|
||||||
|
DB: "temp",
|
||||||
|
Fields: []string{"cpum", "cpub", "sw", "hdd", "fan_speed"},
|
||||||
|
Precision: 10,
|
||||||
|
DateStart: int(time.Now().Unix() - 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
freeboxToken, err := setFreeboxToken(authInf, xSessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
r, err := json.Marshal(*d)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
buf := bytes.NewReader(r)
|
||||||
|
req, err := http.NewRequest(pr.method, fmt.Sprintf(pr.url), buf)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
req.Header.Add(pr.header, *xSessionToken)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 404 {
|
||||||
|
return []int64{}, errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
rrdTest := rrd{}
|
||||||
|
err = json.Unmarshal(body, &rrdTest)
|
||||||
|
if err != nil {
|
||||||
|
if debug {
|
||||||
|
log.Println(string(body))
|
||||||
|
}
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rrdTest.ErrorCode == "auth_required" {
|
||||||
|
*xSessionToken, err = getSessToken(freeboxToken, authInf, xSessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rrdTest.ErrorCode != "" && rrdTest.ErrorCode != "auth_required" {
|
||||||
|
if rrdTest.status().Error() == "Unknown return code from the API" {
|
||||||
|
fmt.Println("getTemp")
|
||||||
|
}
|
||||||
|
return []int64{}, rrdTest.status()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rrdTest.Result.Data) == 0 {
|
||||||
|
return []int64{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return []int64{rrdTest.Result.Data[0]["cpum"], rrdTest.Result.Data[0]["cpub"], rrdTest.Result.Data[0]["sw"], rrdTest.Result.Data[0]["hdd"], rrdTest.Result.Data[0]["fan_speed"]}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNet(authInf *authInfo, pr *postRequest, xSessionToken *string) ([]int64, error) {
|
||||||
|
d := &database{
|
||||||
|
DB: "net",
|
||||||
|
Fields: []string{"bw_up", "bw_down", "rate_up", "rate_down", "vpn_rate_up", "vpn_rate_down"},
|
||||||
|
Precision: 10,
|
||||||
|
DateStart: int(time.Now().Unix() - 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
freeboxToken, err := setFreeboxToken(authInf, xSessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
r, err := json.Marshal(*d)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
buf := bytes.NewReader(r)
|
||||||
|
req, err := http.NewRequest(pr.method, pr.url, buf)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
req.Header.Add(pr.header, *xSessionToken)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 404 {
|
||||||
|
return []int64{}, errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
rrdTest := rrd{}
|
||||||
|
err = json.Unmarshal(body, &rrdTest)
|
||||||
|
if err != nil {
|
||||||
|
if debug {
|
||||||
|
log.Println(string(body))
|
||||||
|
}
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rrdTest.ErrorCode == "auth_required" {
|
||||||
|
*xSessionToken, err = getSessToken(freeboxToken, authInf, xSessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rrdTest.ErrorCode != "" && rrdTest.ErrorCode != "auth_required" {
|
||||||
|
if rrdTest.status().Error() == "Unknown return code from the API" {
|
||||||
|
fmt.Println("getNet")
|
||||||
|
}
|
||||||
|
return []int64{}, rrdTest.status()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rrdTest.Result.Data) == 0 {
|
||||||
|
return []int64{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return []int64{rrdTest.Result.Data[0]["bw_up"], rrdTest.Result.Data[0]["bw_down"], rrdTest.Result.Data[0]["rate_up"], rrdTest.Result.Data[0]["rate_down"], rrdTest.Result.Data[0]["vpn_rate_up"], rrdTest.Result.Data[0]["vpn_rate_down"]}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSwitch(authInf *authInfo, pr *postRequest, xSessionToken *string) ([]int64, error) {
|
||||||
|
d := &database{
|
||||||
|
DB: "switch",
|
||||||
|
Fields: []string{"rx_1", "tx_1", "rx_2", "tx_2", "rx_3", "tx_3", "rx_4", "tx_4"},
|
||||||
|
Precision: 10,
|
||||||
|
DateStart: int(time.Now().Unix() - 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
freeboxToken, err := setFreeboxToken(authInf, xSessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
r, err := json.Marshal(*d)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
buf := bytes.NewReader(r)
|
||||||
|
req, err := http.NewRequest(pr.method, pr.url, buf)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
req.Header.Add(pr.header, *xSessionToken)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 404 {
|
||||||
|
return []int64{}, errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
rrdTest := rrd{}
|
||||||
|
err = json.Unmarshal(body, &rrdTest)
|
||||||
|
if err != nil {
|
||||||
|
if debug {
|
||||||
|
log.Println(string(body))
|
||||||
|
}
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rrdTest.ErrorCode == "auth_required" {
|
||||||
|
*xSessionToken, err = getSessToken(freeboxToken, authInf, xSessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return []int64{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rrdTest.ErrorCode != "" && rrdTest.ErrorCode != "auth_required" {
|
||||||
|
if rrdTest.status().Error() == "Unknown return code from the API" {
|
||||||
|
fmt.Println("getSwitch")
|
||||||
|
}
|
||||||
|
return []int64{}, rrdTest.status()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rrdTest.Result.Data) == 0 {
|
||||||
|
return []int64{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return []int64{rrdTest.Result.Data[0]["rx_1"], rrdTest.Result.Data[0]["tx_1"], rrdTest.Result.Data[0]["rx_2"], rrdTest.Result.Data[0]["tx_2"], rrdTest.Result.Data[0]["rx_3"], rrdTest.Result.Data[0]["tx_3"], rrdTest.Result.Data[0]["rx_4"], rrdTest.Result.Data[0]["tx_4"]}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLan(authInf *authInfo, pr *postRequest, xSessionToken *string) ([]lanHost, error) {
|
||||||
|
freeboxToken, err := setFreeboxToken(authInf, xSessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return []lanHost{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest(pr.method, pr.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return []lanHost{}, err
|
||||||
|
}
|
||||||
|
req.Header.Add(pr.header, *xSessionToken)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return []lanHost{}, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 404 {
|
||||||
|
return []lanHost{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []lanHost{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lanResp := lan{}
|
||||||
|
err = json.Unmarshal(body, &lanResp)
|
||||||
|
if err != nil {
|
||||||
|
if debug {
|
||||||
|
log.Println(string(body))
|
||||||
|
}
|
||||||
|
return []lanHost{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if lanResp.ErrorCode == "auth_required" {
|
||||||
|
*xSessionToken, err = getSessToken(freeboxToken, authInf, xSessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return []lanHost{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if lanResp.ErrorCode != "" && lanResp.ErrorCode != "auth_required" {
|
||||||
|
return []lanHost{}, lanResp.status()
|
||||||
|
}
|
||||||
|
|
||||||
|
return lanResp.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFreeplug(authInf *authInfo, pr *postRequest, xSessionToken *string) (freeplug, error) {
|
||||||
|
if _, err := setFreeboxToken(authInf, xSessionToken); err != nil {
|
||||||
|
return freeplug{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest(pr.method, pr.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return freeplug{}, err
|
||||||
|
}
|
||||||
|
req.Header.Add(pr.header, *xSessionToken)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return freeplug{}, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 404 {
|
||||||
|
return freeplug{}, errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return freeplug{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
freeplugResp := freeplug{}
|
||||||
|
err = json.Unmarshal(body, &freeplugResp)
|
||||||
|
if err != nil {
|
||||||
|
if debug {
|
||||||
|
log.Println(string(body))
|
||||||
|
}
|
||||||
|
return freeplug{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return freeplugResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSystem(authInf *authInfo, pr *postRequest, xSessionToken *string) (system, error) {
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest(pr.method, pr.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return system{}, err
|
||||||
|
}
|
||||||
|
req.Header.Add(pr.header, *xSessionToken)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return system{}, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 404 {
|
||||||
|
return system{}, errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return system{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
systemResp := system{}
|
||||||
|
err = json.Unmarshal(body, &systemResp)
|
||||||
|
if err != nil {
|
||||||
|
if debug {
|
||||||
|
log.Println(string(body))
|
||||||
|
}
|
||||||
|
return system{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return systemResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWifi(authInf *authInfo, pr *postRequest, xSessionToken *string) (wifi, error) {
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest(pr.method, pr.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return wifi{}, err
|
||||||
|
}
|
||||||
|
req.Header.Add(pr.header, *xSessionToken)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return wifi{}, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 404 {
|
||||||
|
return wifi{}, errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return wifi{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
wifiResp := wifi{}
|
||||||
|
err = json.Unmarshal(body, &wifiResp)
|
||||||
|
if err != nil {
|
||||||
|
if debug {
|
||||||
|
log.Println(string(body))
|
||||||
|
}
|
||||||
|
return wifi{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return wifiResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWifiStations(authInf *authInfo, pr *postRequest, xSessionToken *string) (wifiStations, error) {
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest(pr.method, pr.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return wifiStations{}, err
|
||||||
|
}
|
||||||
|
req.Header.Add(pr.header, *xSessionToken)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return wifiStations{}, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 404 {
|
||||||
|
return wifiStations{}, errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return wifiStations{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
wifiStationResp := wifiStations{}
|
||||||
|
err = json.Unmarshal(body, &wifiStationResp)
|
||||||
|
if err != nil {
|
||||||
|
if debug {
|
||||||
|
log.Println(string(body))
|
||||||
|
}
|
||||||
|
return wifiStations{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return wifiStationResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVpnServer(authInf *authInfo, pr *postRequest, xSessionToken *string) (vpnServer, error) {
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest(pr.method, pr.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return vpnServer{}, err
|
||||||
|
}
|
||||||
|
req.Header.Add(pr.header, *xSessionToken)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return vpnServer{}, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 404 {
|
||||||
|
return vpnServer{}, errors.New(resp.Status)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return vpnServer{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vpnServerResp := vpnServer{}
|
||||||
|
err = json.Unmarshal(body, &vpnServerResp)
|
||||||
|
if err != nil {
|
||||||
|
if debug {
|
||||||
|
log.Println(string(body))
|
||||||
|
}
|
||||||
|
return vpnServer{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return vpnServerResp, nil
|
||||||
|
}
|
727
getters_test.go
Normal file
727
getters_test.go
Normal file
@ -0,0 +1,727 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetFreeboxToken(t *testing.T) {
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.RequestURI {
|
||||||
|
case "/login":
|
||||||
|
myChall := &challenge{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myChall.Result.Challenge = "foobar"
|
||||||
|
result, _ := json.Marshal(myChall)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/session":
|
||||||
|
myToken := sessionToken{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myToken.Result.SessionToken = "foobar"
|
||||||
|
result, _ := json.Marshal(myToken)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/granted/":
|
||||||
|
myTrack := track{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myTrack.Result.TrackID = 101
|
||||||
|
result, _ := json.Marshal(myTrack)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/granted/101":
|
||||||
|
myGrant := grant{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myGrant.Result.Status = "granted"
|
||||||
|
result, _ := json.Marshal(myGrant)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
default:
|
||||||
|
fmt.Fprintln(w, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
ai := &authInfo{}
|
||||||
|
ai.myStore.location = "/tmp/token"
|
||||||
|
ai.myAPI.login = ts.URL + "/login"
|
||||||
|
ai.myAPI.loginSession = ts.URL + "/session"
|
||||||
|
ai.myAPI.authz = ts.URL + "/granted/"
|
||||||
|
ai.myReader = bufio.NewReader(strings.NewReader("\n"))
|
||||||
|
|
||||||
|
var mySessionToken string
|
||||||
|
|
||||||
|
token, err := setFreeboxToken(ai, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(ai.myStore.location)
|
||||||
|
|
||||||
|
if token != "foobar" {
|
||||||
|
t.Error("Expected foobar, but got", token)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("FREEBOX_TOKEN", "barfoo")
|
||||||
|
defer os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
token, err = setFreeboxToken(ai, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if token != "barfoo" {
|
||||||
|
t.Error("Expected barfoo, but got", token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDsl(t *testing.T) {
|
||||||
|
os.Setenv("FREEBOX_TOKEN", "IOI")
|
||||||
|
defer os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.RequestURI {
|
||||||
|
case "/good":
|
||||||
|
myRRD := rrd{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myRRD.Result.Data = []map[string]int64{
|
||||||
|
{
|
||||||
|
"rate_up": 12,
|
||||||
|
"rate_down": 34,
|
||||||
|
"snr_up": 56,
|
||||||
|
"snr_down": 78,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myRRD)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/error":
|
||||||
|
myRRD := rrd{
|
||||||
|
Success: true,
|
||||||
|
ErrorCode: "insufficient_rights",
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myRRD)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/null":
|
||||||
|
myRRD := rrd{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myRRD)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
goodPR := &postRequest{
|
||||||
|
method: "POST",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/good",
|
||||||
|
}
|
||||||
|
|
||||||
|
errorPR := &postRequest{
|
||||||
|
method: "POST",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/error",
|
||||||
|
}
|
||||||
|
|
||||||
|
nullPR := &postRequest{
|
||||||
|
method: "POST",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/null",
|
||||||
|
}
|
||||||
|
|
||||||
|
ai := &authInfo{}
|
||||||
|
mySessionToken := "foobar"
|
||||||
|
|
||||||
|
getDslResult, err := getDsl(ai, goodPR, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if getDslResult[0] != 12 || getDslResult[1] != 34 || getDslResult[2] != 56 || getDslResult[3] != 78 {
|
||||||
|
t.Errorf("Expected 12 34 56 78, but got %v %v %v %v\n", getDslResult[0], getDslResult[1], getDslResult[2], getDslResult[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
getDslResult, err = getDsl(ai, errorPR, &mySessionToken)
|
||||||
|
if err.Error() != "Your app permissions does not allow accessing this API" {
|
||||||
|
t.Error("Expected Your app permissions does not allow accessing this API, but go", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(getDslResult) != 0 {
|
||||||
|
t.Error("Expected 0, but got", len(getDslResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
getDslResult, err = getDsl(ai, nullPR, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(getDslResult) != 0 {
|
||||||
|
t.Error("Expected 0, but got", len(getDslResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTemp(t *testing.T) {
|
||||||
|
os.Setenv("FREEBOX_TOKEN", "IOI")
|
||||||
|
defer os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.RequestURI {
|
||||||
|
case "/good":
|
||||||
|
myRRD := rrd{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myRRD.Result.Data = []map[string]int64{
|
||||||
|
{
|
||||||
|
"cpum": 01,
|
||||||
|
"cpub": 02,
|
||||||
|
"sw": 03,
|
||||||
|
"hdd": 04,
|
||||||
|
"fan_speed": 05,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myRRD)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/error":
|
||||||
|
myRRD := rrd{
|
||||||
|
Success: true,
|
||||||
|
ErrorCode: "denied_from_external_ip",
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myRRD)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/null":
|
||||||
|
myRRD := rrd{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myRRD)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
goodPR := &postRequest{
|
||||||
|
method: "POST",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/good",
|
||||||
|
}
|
||||||
|
|
||||||
|
errorPR := &postRequest{
|
||||||
|
method: "POST",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/error",
|
||||||
|
}
|
||||||
|
|
||||||
|
nullPR := &postRequest{
|
||||||
|
method: "POST",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/null",
|
||||||
|
}
|
||||||
|
|
||||||
|
ai := &authInfo{}
|
||||||
|
mySessionToken := "foobar"
|
||||||
|
|
||||||
|
getTempResult, err := getTemp(ai, goodPR, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if getTempResult[0] != 01 || getTempResult[1] != 02 || getTempResult[2] != 03 || getTempResult[3] != 04 || getTempResult[4] != 05 {
|
||||||
|
t.Errorf("Expected 01 02 03 04 05, but got %v %v %v %v %v\n", getTempResult[0], getTempResult[1], getTempResult[2], getTempResult[3], getTempResult[4])
|
||||||
|
}
|
||||||
|
|
||||||
|
getTempResult, err = getTemp(ai, errorPR, &mySessionToken)
|
||||||
|
if err.Error() != "You are trying to get an app_token from a remote IP" {
|
||||||
|
t.Error("Expected You are trying to get an app_token from a remote IP, but go", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(getTempResult) != 0 {
|
||||||
|
t.Error("Expected 0, but got", len(getTempResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
getTempResult, err = getTemp(ai, nullPR, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(getTempResult) != 0 {
|
||||||
|
t.Error("Expected 0, but got", len(getTempResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNet(t *testing.T) {
|
||||||
|
os.Setenv("FREEBOX_TOKEN", "IOI")
|
||||||
|
defer os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.RequestURI {
|
||||||
|
case "/good":
|
||||||
|
myRRD := rrd{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myRRD.Result.Data = []map[string]int64{
|
||||||
|
{
|
||||||
|
"bw_up": 12500000000,
|
||||||
|
"bw_down": 12500000000,
|
||||||
|
"rate_up": 12500000000,
|
||||||
|
"rate_down": 12500000000,
|
||||||
|
"vpn_rate_up": 12500000000,
|
||||||
|
"vpn_rate_down": 12500000000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myRRD)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/error":
|
||||||
|
myRRD := rrd{
|
||||||
|
Success: true,
|
||||||
|
ErrorCode: "new_apps_denied",
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myRRD)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/null":
|
||||||
|
myRRD := rrd{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myRRD)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
goodPR := &postRequest{
|
||||||
|
method: "POST",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/good",
|
||||||
|
}
|
||||||
|
|
||||||
|
errorPR := &postRequest{
|
||||||
|
method: "POST",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/error",
|
||||||
|
}
|
||||||
|
|
||||||
|
nullPR := &postRequest{
|
||||||
|
method: "POST",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/null",
|
||||||
|
}
|
||||||
|
|
||||||
|
ai := &authInfo{}
|
||||||
|
mySessionToken := "foobar"
|
||||||
|
|
||||||
|
getNetResult, err := getNet(ai, goodPR, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but go", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if getNetResult[0] != 12500000000 || getNetResult[1] != 12500000000 || getNetResult[2] != 12500000000 || getNetResult[3] != 12500000000 || getNetResult[4] != 12500000000 || getNetResult[5] != 12500000000 {
|
||||||
|
t.Errorf("Expected 01 02 03 04 05 06, but got %v %v %v %v %v %v\n", getNetResult[0], getNetResult[1], getNetResult[2], getNetResult[3], getNetResult[4], getNetResult[5])
|
||||||
|
}
|
||||||
|
|
||||||
|
getNetResult, err = getNet(ai, errorPR, &mySessionToken)
|
||||||
|
if err.Error() != "New application token request has been disabled" {
|
||||||
|
t.Error("Expected New application token request has been disabled, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(getNetResult) != 0 {
|
||||||
|
t.Error("Expected 0, but got", len(getNetResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
getNetResult, err = getNet(ai, nullPR, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(getNetResult) != 0 {
|
||||||
|
t.Error("Expected 0, but got", len(getNetResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSwitch(t *testing.T) {
|
||||||
|
os.Setenv("FREEBOX_TOKEN", "IOI")
|
||||||
|
defer os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.RequestURI {
|
||||||
|
case "/good":
|
||||||
|
myRRD := rrd{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myRRD.Result.Data = []map[string]int64{
|
||||||
|
{
|
||||||
|
"rx_1": 01,
|
||||||
|
"tx_1": 11,
|
||||||
|
"rx_2": 02,
|
||||||
|
"tx_2": 12,
|
||||||
|
"rx_3": 03,
|
||||||
|
"tx_3": 13,
|
||||||
|
"rx_4": 04,
|
||||||
|
"tx_4": 14,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myRRD)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/error":
|
||||||
|
myRRD := rrd{
|
||||||
|
Success: true,
|
||||||
|
ErrorCode: "apps_denied",
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myRRD)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/null":
|
||||||
|
myRRD := rrd{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myRRD)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
goodPR := &postRequest{
|
||||||
|
method: "POST",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/good",
|
||||||
|
}
|
||||||
|
|
||||||
|
errorPR := &postRequest{
|
||||||
|
method: "POST",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/error",
|
||||||
|
}
|
||||||
|
|
||||||
|
nullPR := &postRequest{
|
||||||
|
method: "POST",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/null",
|
||||||
|
}
|
||||||
|
|
||||||
|
ai := &authInfo{}
|
||||||
|
mySessionToken := "foobar"
|
||||||
|
|
||||||
|
getSwitchResult, err := getSwitch(ai, goodPR, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if getSwitchResult[0] != 01 || getSwitchResult[1] != 11 || getSwitchResult[2] != 02 || getSwitchResult[3] != 12 || getSwitchResult[4] != 03 || getSwitchResult[5] != 13 || getSwitchResult[6] != 04 || getSwitchResult[7] != 14 {
|
||||||
|
t.Errorf("Expected 01 11 02 12 03 13 04 14, but got %v %v %v %v %v %v %v %v\n", getSwitchResult[0], getSwitchResult[1], getSwitchResult[2], getSwitchResult[3], getSwitchResult[4], getSwitchResult[5], getSwitchResult[6], getSwitchResult[7])
|
||||||
|
}
|
||||||
|
|
||||||
|
getSwitchResult, err = getSwitch(ai, errorPR, &mySessionToken)
|
||||||
|
if err.Error() != "API access from apps has been disabled" {
|
||||||
|
t.Error("Expected API access from apps has been disabled, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(getSwitchResult) != 0 {
|
||||||
|
t.Error("Expected 0, but got", len(getSwitchResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
getSwitchResult, err = getSwitch(ai, nullPR, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(getSwitchResult) != 0 {
|
||||||
|
t.Error("Expected 0, but got", len(getSwitchResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetLan(t *testing.T) {
|
||||||
|
os.Setenv("FREEBOX_TOKEN", "IOI")
|
||||||
|
defer os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.RequestURI {
|
||||||
|
case "/good":
|
||||||
|
myLan := lan{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myLan.Result = []lanHost{
|
||||||
|
{
|
||||||
|
Reachable: true,
|
||||||
|
PrimaryName: "Reachable host",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Reachable: false,
|
||||||
|
PrimaryName: "Unreachable host",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myLan)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
case "/error":
|
||||||
|
myLan := lan{
|
||||||
|
Success: true,
|
||||||
|
ErrorCode: "ratelimited",
|
||||||
|
}
|
||||||
|
result, _ := json.Marshal(myLan)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
goodPR := &postRequest{
|
||||||
|
method: "GET",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/good",
|
||||||
|
}
|
||||||
|
|
||||||
|
errorPR := &postRequest{
|
||||||
|
method: "GET",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL + "/error",
|
||||||
|
}
|
||||||
|
|
||||||
|
ai := &authInfo{}
|
||||||
|
mySessionToken := "foobar"
|
||||||
|
|
||||||
|
lanAvailable, err := getLan(ai, goodPR, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range lanAvailable {
|
||||||
|
if v.Reachable && v.PrimaryName != "Reachable host" {
|
||||||
|
t.Errorf("Expected Reachable: true, Host: Reachable host, but go Reachable: %v, Host: %v", v.Reachable, v.PrimaryName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.Reachable && v.PrimaryName != "Unreachable host" {
|
||||||
|
t.Errorf("Expected Reachable: false, Host: Unreachable host, but go Reachable: %v, Host: %v", !v.Reachable, v.PrimaryName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lanAvailable, err = getLan(ai, errorPR, &mySessionToken)
|
||||||
|
if err.Error() != "Too many auth error have been made from your IP" {
|
||||||
|
t.Error("Expected Too many auth error have been made from your IP, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSystem(t *testing.T) {
|
||||||
|
os.Setenv("FREEBOX_TOKEN", "IOI")
|
||||||
|
defer os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mySys := system{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
mySys.Result.FanRPM = 666
|
||||||
|
mySys.Result.TempCpub = 81
|
||||||
|
mySys.Result.TempCpum = 89
|
||||||
|
mySys.Result.TempHDD = 30
|
||||||
|
mySys.Result.TempSW = 54
|
||||||
|
|
||||||
|
/*
|
||||||
|
mySys.Result {
|
||||||
|
FanRPM: 666,
|
||||||
|
TempCpub: 81,
|
||||||
|
TempCpum: 89,
|
||||||
|
TempHDD: 30,
|
||||||
|
TempSW: 54,
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
result, _ := json.Marshal(mySys)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
pr := &postRequest{
|
||||||
|
method: "GET",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
ai := &authInfo{}
|
||||||
|
mySessionToken := "foobar"
|
||||||
|
|
||||||
|
systemStats, err := getSystem(ai, pr, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if systemStats.Result.FanRPM != 666 {
|
||||||
|
t.Error("Expected 666, but got", systemStats.Result.FanRPM)
|
||||||
|
}
|
||||||
|
|
||||||
|
if systemStats.Result.TempCpub != 81 {
|
||||||
|
t.Error("Expected 81, but got", systemStats.Result.TempCpub)
|
||||||
|
}
|
||||||
|
|
||||||
|
if systemStats.Result.TempCpum != 89 {
|
||||||
|
t.Error("Expected 89, but got", systemStats.Result.TempCpum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if systemStats.Result.TempHDD != 30 {
|
||||||
|
t.Error("Expected 30, but got", systemStats.Result.TempHDD)
|
||||||
|
}
|
||||||
|
|
||||||
|
if systemStats.Result.TempSW != 54 {
|
||||||
|
t.Error("Expected 54, but got", systemStats.Result.TempSW)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetWifi(t *testing.T) {
|
||||||
|
os.Setenv("FREEBOX_TOKEN", "IOI")
|
||||||
|
defer os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
myWifi := wifi{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
myAP := wifiAccessPoint{
|
||||||
|
Name: "AP1",
|
||||||
|
ID: 0,
|
||||||
|
}
|
||||||
|
myWifi.Result = []wifiAccessPoint{myAP}
|
||||||
|
|
||||||
|
result, _ := json.Marshal(myWifi)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
pr := &postRequest{
|
||||||
|
method: "GET",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
ai := &authInfo{}
|
||||||
|
mySessionToken := "foobar"
|
||||||
|
|
||||||
|
wifiStats, err := getWifi(ai, pr, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wifiStats.Result[0].Name != "AP1" {
|
||||||
|
t.Error("Expected AP1, but got", wifiStats.Result[0].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wifiStats.Result[0].ID != 0 {
|
||||||
|
t.Error("Expected 0, but got", wifiStats.Result[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetWifiStations(t *testing.T) {
|
||||||
|
os.Setenv("FREEBOX_TOKEN", "IOI")
|
||||||
|
defer os.Unsetenv("FREEBOX_TOKEN")
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
myWifiStations := wifiStations{
|
||||||
|
Success: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
myStation := wifiStation{
|
||||||
|
Hostname: "station_host",
|
||||||
|
MAC: "AA:BB:CC:DD:EE:FF",
|
||||||
|
State: "authorized",
|
||||||
|
Inactive: 60,
|
||||||
|
RXBytes: 500,
|
||||||
|
TXBytes: 2280000000,
|
||||||
|
ConnectionDuration: 600,
|
||||||
|
TXRate: 4260000000,
|
||||||
|
RXRate: 5,
|
||||||
|
Signal: -20,
|
||||||
|
}
|
||||||
|
myWifiStations.Result = []wifiStation{myStation}
|
||||||
|
|
||||||
|
result, _ := json.Marshal(myWifiStations)
|
||||||
|
fmt.Fprintln(w, string(result))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
pr := &postRequest{
|
||||||
|
method: "GET",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
url: ts.URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
ai := &authInfo{}
|
||||||
|
mySessionToken := "foobar"
|
||||||
|
|
||||||
|
wifiStationsStats, err := getWifiStations(ai, pr, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected no err, but got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wifiStationsStats.Result[0].Hostname != "station_host" {
|
||||||
|
t.Error("Expected station_host, but got", wifiStationsStats.Result[0].Hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wifiStationsStats.Result[0].MAC != "AA:BB:CC:DD:EE:FF" {
|
||||||
|
t.Error("Expected AA:BB:CC:DD:EE:FF, but got", wifiStationsStats.Result[0].MAC)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wifiStationsStats.Result[0].State != "authorized" {
|
||||||
|
t.Error("Expected authorized, but got", wifiStationsStats.Result[0].State)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wifiStationsStats.Result[0].Inactive != 60 {
|
||||||
|
t.Error("Expected 60, but got", wifiStationsStats.Result[0].Inactive)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wifiStationsStats.Result[0].RXBytes != 500 {
|
||||||
|
t.Error("Expected 500, but got", wifiStationsStats.Result[0].RXBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wifiStationsStats.Result[0].TXBytes != 10000 {
|
||||||
|
t.Error("Expected 10000, but got", wifiStationsStats.Result[0].TXBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wifiStationsStats.Result[0].ConnectionDuration != 600 {
|
||||||
|
t.Error("Expected 600, but got", wifiStationsStats.Result[0].ConnectionDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wifiStationsStats.Result[0].TXRate != 20 {
|
||||||
|
t.Error("Expected 20, but got", wifiStationsStats.Result[0].TXRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wifiStationsStats.Result[0].RXRate != 5 {
|
||||||
|
t.Error("Expected 5, but got", wifiStationsStats.Result[0].RXRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wifiStationsStats.Result[0].Signal != -20 {
|
||||||
|
t.Error("Expected -20, but got", wifiStationsStats.Result[0].Signal)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getNet(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
authInf *authInfo
|
||||||
|
pr *postRequest
|
||||||
|
xSessionToken *string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []int
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
// TODO: Add test cases.
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := getNet(tt.args.authInf, tt.args.pr, tt.args.xSessionToken)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("getNet() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("getNet() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
9
go.mod
Normal file
9
go.mod
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module freebox_exporter
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/golang/protobuf v1.2.1-0.20190109072247-347cf4a86c1c // indirect
|
||||||
|
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334
|
||||||
|
github.com/prometheus/client_golang v0.9.2
|
||||||
|
)
|
23
go.sum
Normal file
23
go.sum
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.2.1-0.20190109072247-347cf4a86c1c h1:fQ4P1oAipLwec/j5tfZTYV/e5i9ICSk23uVL+TK9III=
|
||||||
|
github.com/golang/protobuf v1.2.1-0.20190109072247-347cf4a86c1c/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
|
||||||
|
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8=
|
||||||
|
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
|
||||||
|
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
|
||||||
|
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
310
main.go
Normal file
310
main.go
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/iancoleman/strcase"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mafreebox string
|
||||||
|
listen string
|
||||||
|
debug bool
|
||||||
|
fiber bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&mafreebox, "endpoint", "http://mafreebox.freebox.fr/", "Endpoint for freebox API")
|
||||||
|
flag.StringVar(&listen, "listen", ":10001", "Prometheus metrics port")
|
||||||
|
flag.BoolVar(&debug, "debug", false, "Debug mode")
|
||||||
|
flag.BoolVar(&fiber, "fiber", false, "Turn on if you're using a fiber Freebox")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(mafreebox, "/") {
|
||||||
|
mafreebox = mafreebox + "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := mafreebox + "api/v4/login/"
|
||||||
|
myAuthInfo := &authInfo{
|
||||||
|
myAPI: api{
|
||||||
|
login: endpoint,
|
||||||
|
authz: endpoint + "authorize/",
|
||||||
|
loginSession: endpoint + "session/",
|
||||||
|
},
|
||||||
|
myStore: store{location: os.Getenv("HOME") + "/.freebox_token"},
|
||||||
|
myApp: app{
|
||||||
|
AppID: "fr.freebox.exporter",
|
||||||
|
AppName: "prometheus-exporter",
|
||||||
|
AppVersion: "0.4",
|
||||||
|
DeviceName: "local",
|
||||||
|
},
|
||||||
|
myReader: bufio.NewReader(os.Stdin),
|
||||||
|
}
|
||||||
|
|
||||||
|
myPostRequest := newPostRequest()
|
||||||
|
|
||||||
|
myConnectionXdslRequest := &postRequest{
|
||||||
|
method: "GET",
|
||||||
|
url: mafreebox + "api/v4/connection/xdsl/",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
}
|
||||||
|
|
||||||
|
myFreeplugRequest := &postRequest{
|
||||||
|
method: "GET",
|
||||||
|
url: mafreebox + "api/v4/freeplug/",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
}
|
||||||
|
|
||||||
|
myLanRequest := &postRequest{
|
||||||
|
method: "GET",
|
||||||
|
url: mafreebox + "api/v4/lan/browser/pub/",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
}
|
||||||
|
|
||||||
|
mySystemRequest := &postRequest{
|
||||||
|
method: "GET",
|
||||||
|
url: mafreebox + "api/v4/system/",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
}
|
||||||
|
|
||||||
|
myWifiRequest := &postRequest{
|
||||||
|
method: "GET",
|
||||||
|
url: mafreebox + "api/v2/wifi/ap/",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
}
|
||||||
|
|
||||||
|
myVpnRequest := &postRequest{
|
||||||
|
method: "GET",
|
||||||
|
url: mafreebox + "api/v4/vpn/connection/",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
}
|
||||||
|
|
||||||
|
var mySessionToken string
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
// There is no DSL metric on fiber Freebox
|
||||||
|
// If you use a fiber Freebox, use -fiber flag to turn off this metric
|
||||||
|
if !fiber {
|
||||||
|
// connectionXdsl metrics
|
||||||
|
connectionXdslStats, err := getConnectionXdsl(myAuthInfo, myConnectionXdslRequest, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("An error occured with connectionXdsl metrics: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if connectionXdslStats.Success {
|
||||||
|
status := connectionXdslStats.Result.Status
|
||||||
|
result := connectionXdslStats.Result
|
||||||
|
down := result.Down
|
||||||
|
up := result.Up
|
||||||
|
|
||||||
|
connectionXdslStatusUptimeGauges.
|
||||||
|
WithLabelValues(status.Status, status.Protocol, status.Modulation).
|
||||||
|
Set(float64(status.Uptime))
|
||||||
|
|
||||||
|
connectionXdslDownAttnGauge.Set(float64(down.Attn10) / 10)
|
||||||
|
connectionXdslUpAttnGauge.Set(float64(up.Attn10) / 10)
|
||||||
|
|
||||||
|
// XXX: sometimes the Freebox is reporting zero as SNR which
|
||||||
|
// does not make sense so we don't log these
|
||||||
|
if down.Snr10 > 0 {
|
||||||
|
connectionXdslDownSnrGauge.Set(float64(down.Snr10) / 10)
|
||||||
|
}
|
||||||
|
if up.Snr10 > 0 {
|
||||||
|
connectionXdslUpSnrGauge.Set(float64(up.Snr10) / 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionXdslNitroGauges.WithLabelValues("down").
|
||||||
|
Set(bool2float(down.Nitro))
|
||||||
|
connectionXdslNitroGauges.WithLabelValues("up").
|
||||||
|
Set(bool2float(up.Nitro))
|
||||||
|
|
||||||
|
connectionXdslGinpGauges.WithLabelValues("down", "enabled").
|
||||||
|
Set(bool2float(down.Ginp))
|
||||||
|
connectionXdslGinpGauges.WithLabelValues("up", "enabled").
|
||||||
|
Set(bool2float(up.Ginp))
|
||||||
|
|
||||||
|
logFields(result, connectionXdslGinpGauges,
|
||||||
|
[]string{"rtx_tx", "rtx_c", "rtx_uc"})
|
||||||
|
|
||||||
|
logFields(result, connectionXdslErrorGauges,
|
||||||
|
[]string{"crc", "es", "fec", "hec", "ses"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// dsl metrics
|
||||||
|
getDslResult, err := getDsl(myAuthInfo, myPostRequest, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("An error occured with DSL metrics: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(getDslResult) > 0 {
|
||||||
|
rateUpGauge.Set(float64(getDslResult[0]))
|
||||||
|
rateDownGauge.Set(float64(getDslResult[1]))
|
||||||
|
snrUpGauge.Set(float64(getDslResult[2]))
|
||||||
|
snrDownGauge.Set(float64(getDslResult[3]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// freeplug metrics
|
||||||
|
freeplugStats, err := getFreeplug(myAuthInfo, myFreeplugRequest, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("An error occured with freeplug metrics: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, freeplugNetwork := range freeplugStats.Result {
|
||||||
|
for _, freeplugMember := range freeplugNetwork.Members {
|
||||||
|
if freeplugMember.HasNetwork {
|
||||||
|
freeplugHasNetworkGauge.WithLabelValues(freeplugMember.ID).Set(float64(1))
|
||||||
|
} else {
|
||||||
|
freeplugHasNetworkGauge.WithLabelValues(freeplugMember.ID).Set(float64(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
Mb := 1e6
|
||||||
|
rxRate := float64(freeplugMember.RxRate) * Mb
|
||||||
|
txRate := float64(freeplugMember.TxRate) * Mb
|
||||||
|
|
||||||
|
if rxRate >= 0 { // -1 if not unavailable
|
||||||
|
freeplugRxRateGauge.WithLabelValues(freeplugMember.ID).Set(rxRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if txRate >= 0 { // -1 if not unavailable
|
||||||
|
freeplugTxRateGauge.WithLabelValues(freeplugMember.ID).Set(txRate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// net metrics
|
||||||
|
getNetResult, err := getNet(myAuthInfo, myPostRequest, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("An error occured with NET metrics: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(getNetResult) > 0 {
|
||||||
|
bwUpGauge.Set(float64(getNetResult[0]))
|
||||||
|
bwDownGauge.Set(float64(getNetResult[1]))
|
||||||
|
netRateUpGauge.Set(float64(getNetResult[2]))
|
||||||
|
netRateDownGauge.Set(float64(getNetResult[3]))
|
||||||
|
vpnRateUpGauge.Set(float64(getNetResult[4]))
|
||||||
|
vpnRateDownGauge.Set(float64(getNetResult[5]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// lan metrics
|
||||||
|
lanAvailable, err := getLan(myAuthInfo, myLanRequest, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("An error occured with LAN metrics: %v", err)
|
||||||
|
}
|
||||||
|
for _, v := range lanAvailable {
|
||||||
|
var Ip string
|
||||||
|
if len(v.L3c) > 0 {
|
||||||
|
Ip = v.L3c[0].Addr
|
||||||
|
} else {
|
||||||
|
Ip = ""
|
||||||
|
}
|
||||||
|
if v.Reachable {
|
||||||
|
lanReachableGauges.With(prometheus.Labels{"name": v.PrimaryName, "vendor":v.Vendor_name, "ip": Ip}).Set(float64(1))
|
||||||
|
} else {
|
||||||
|
lanReachableGauges.With(prometheus.Labels{"name": v.PrimaryName, "vendor":v.Vendor_name, "ip": Ip}).Set(float64(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// system metrics
|
||||||
|
systemStats, err := getSystem(myAuthInfo, mySystemRequest, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("An error occured with System metrics: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
systemTempGauges.WithLabelValues("Température CPU B").Set(float64(systemStats.Result.TempCpub))
|
||||||
|
systemTempGauges.WithLabelValues("Température CPU M").Set(float64(systemStats.Result.TempCpum))
|
||||||
|
systemTempGauges.WithLabelValues("Température Switch").Set(float64(systemStats.Result.TempSW))
|
||||||
|
systemTempGauges.WithLabelValues("Disque dur").Set(float64(systemStats.Result.TempHDD))
|
||||||
|
systemFanGauges.WithLabelValues("Ventilateur 1").Set(float64(systemStats.Result.FanRPM))
|
||||||
|
|
||||||
|
systemUptimeGauges.
|
||||||
|
WithLabelValues(systemStats.Result.FirmwareVersion).
|
||||||
|
Set(float64(systemStats.Result.UptimeVal))
|
||||||
|
|
||||||
|
// wifi metrics
|
||||||
|
wifiStats, err := getWifi(myAuthInfo, myWifiRequest, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("An error occured with Wifi metrics: %v", err)
|
||||||
|
}
|
||||||
|
for _, accessPoint := range wifiStats.Result {
|
||||||
|
myWifiStationRequest := &postRequest{
|
||||||
|
method: "GET",
|
||||||
|
url: mafreebox + "api/v2/wifi/ap/" + strconv.Itoa(accessPoint.ID) + "/stations",
|
||||||
|
header: "X-Fbx-App-Auth",
|
||||||
|
}
|
||||||
|
wifiStationsStats, err := getWifiStations(myAuthInfo, myWifiStationRequest, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("An error occured with Wifi station metrics: %v", err)
|
||||||
|
}
|
||||||
|
for _, station := range wifiStationsStats.Result {
|
||||||
|
wifiSignalGauges.With(prometheus.Labels{"access_point": accessPoint.Name, "hostname": station.Hostname, "state": station.State}).Set(float64(station.Signal))
|
||||||
|
wifiInactiveGauges.With(prometheus.Labels{"access_point": accessPoint.Name, "hostname": station.Hostname, "state": station.State}).Set(float64(station.Inactive))
|
||||||
|
wifiConnectionDurationGauges.With(prometheus.Labels{"access_point": accessPoint.Name, "hostname": station.Hostname, "state": station.State}).Set(float64(station.ConnectionDuration))
|
||||||
|
wifiRXBytesGauges.With(prometheus.Labels{"access_point": accessPoint.Name, "hostname": station.Hostname, "state": station.State}).Set(float64(station.RXBytes))
|
||||||
|
wifiTXBytesGauges.With(prometheus.Labels{"access_point": accessPoint.Name, "hostname": station.Hostname, "state": station.State}).Set(float64(station.TXBytes))
|
||||||
|
wifiRXRateGauges.With(prometheus.Labels{"access_point": accessPoint.Name, "hostname": station.Hostname, "state": station.State}).Set(float64(station.RXRate))
|
||||||
|
wifiTXRateGauges.With(prometheus.Labels{"access_point": accessPoint.Name, "hostname": station.Hostname, "state": station.State}).Set(float64(station.TXRate))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VPN Server Connections List
|
||||||
|
getVpnServerResult, err := getVpnServer(myAuthInfo, myVpnRequest, &mySessionToken)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("An error occured with VPN station metrics: %v", err)
|
||||||
|
}
|
||||||
|
for _, connection := range getVpnServerResult.Result {
|
||||||
|
vpnServerConnectionsList.With(prometheus.Labels{"user": connection.User, "vpn": connection.Vpn, "src_ip": connection.SrcIP, "local_ip": connection.LocalIP, "name": "rx_bytes"}).Set(float64(connection.RxBytes))
|
||||||
|
vpnServerConnectionsList.With(prometheus.Labels{"user": connection.User, "vpn": connection.Vpn, "src_ip": connection.SrcIP, "local_ip": connection.LocalIP, "name": "tx_bytes"}).Set(float64(connection.TxBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("freebox_exporter started on port", listen)
|
||||||
|
http.Handle("/metrics", promhttp.Handler())
|
||||||
|
log.Fatal(http.ListenAndServe(listen, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func logFields(result interface{}, gauge *prometheus.GaugeVec, fields []string) error {
|
||||||
|
resultReflect := reflect.ValueOf(result)
|
||||||
|
|
||||||
|
for _, direction := range []string{"down", "up"} {
|
||||||
|
for _, field := range fields {
|
||||||
|
value := reflect.Indirect(resultReflect).
|
||||||
|
FieldByName(strcase.ToCamel(direction)).
|
||||||
|
FieldByName(strcase.ToCamel(field))
|
||||||
|
|
||||||
|
if value.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
gauge.WithLabelValues(direction, field).
|
||||||
|
Set(float64(value.Int()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bool2float(b bool) float64 {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
276
structs.go
Normal file
276
structs.go
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "bufio"
|
||||||
|
|
||||||
|
type track struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Result struct {
|
||||||
|
AppToken string `json:"app_token"`
|
||||||
|
TrackID int `json:"track_id"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type grant struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Result struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Challenge string `json:"challenge"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type challenge struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Result struct {
|
||||||
|
LoggedIN bool `json:"logged_in,omitempty"`
|
||||||
|
Challenge string `json:"challenge"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type session struct {
|
||||||
|
AppID string `json:"app_id"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type sessionToken struct {
|
||||||
|
Msg string `json:"msg,omitempty"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
UID string `json:"uid,omitempty"`
|
||||||
|
ErrorCode string `json:"error_code,omitempty"`
|
||||||
|
Result struct {
|
||||||
|
SessionToken string `json:"session_token,omitempty"`
|
||||||
|
Challenge string `json:"challenge"`
|
||||||
|
Permissions struct {
|
||||||
|
Settings bool `json:"settings,omitempty"`
|
||||||
|
Contacts bool `json:"contacts,omitempty"`
|
||||||
|
Calls bool `json:"calls,omitempty"`
|
||||||
|
Explorer bool `json:"explorer,omitempty"`
|
||||||
|
Downloader bool `json:"downloader,omitempty"`
|
||||||
|
Parental bool `json:"parental,omitempty"`
|
||||||
|
Pvr bool `json:"pvr,omitempty"`
|
||||||
|
Home bool `json:"home,omitempty"`
|
||||||
|
Camera bool `json:"camera,omitempty"`
|
||||||
|
} `json:"permissions,omitempty"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type rrd struct {
|
||||||
|
UID string `json:"uid,omitempty"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Msg string `json:"msg,omitempty"`
|
||||||
|
Result struct {
|
||||||
|
DateStart int `json:"date_start,omitempty"`
|
||||||
|
DateEnd int `json:"date_end,omitempty"`
|
||||||
|
Data []map[string]int64 `json:"data,omitempty"`
|
||||||
|
} `json:"result"`
|
||||||
|
ErrorCode string `json:"error_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://dev.freebox.fr/sdk/os/connection/
|
||||||
|
type connectionXdsl struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Result struct {
|
||||||
|
Status struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Modulation string `json:"modulation"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Uptime int `json:"uptime"`
|
||||||
|
} `json:"status"`
|
||||||
|
Down struct {
|
||||||
|
Attn int `json:"attn"`
|
||||||
|
Attn10 int `json:"attn_10"`
|
||||||
|
Crc int `json:"crc"`
|
||||||
|
Es int `json:"es"`
|
||||||
|
Fec int `json:"fec"`
|
||||||
|
Ginp bool `json:"ginp"`
|
||||||
|
Hec int `json:"hec"`
|
||||||
|
Maxrate uint64 `json:"maxrate"`
|
||||||
|
Nitro bool `json:"nitro"`
|
||||||
|
Phyr bool `json:"phyr"`
|
||||||
|
Rate int `json:"rate"`
|
||||||
|
RtxC int `json:"rtx_c,omitempty"`
|
||||||
|
RtxTx int `json:"rtx_tx,omitempty"`
|
||||||
|
RtxUc int `json:"rtx_uc,omitempty"`
|
||||||
|
Rxmt int `json:"rxmt"`
|
||||||
|
RxmtCorr int `json:"rxmt_corr"`
|
||||||
|
RxmtUncorr int `json:"rxmt_uncorr"`
|
||||||
|
Ses int `json:"ses"`
|
||||||
|
Snr int `json:"snr"`
|
||||||
|
Snr10 int `json:"snr_10"`
|
||||||
|
} `json:"down"`
|
||||||
|
Up struct {
|
||||||
|
Attn int `json:"attn"`
|
||||||
|
Attn10 int `json:"attn_10"`
|
||||||
|
Crc int `json:"crc"`
|
||||||
|
Es int `json:"es"`
|
||||||
|
Fec int `json:"fec"`
|
||||||
|
Ginp bool `json:"ginp"`
|
||||||
|
Hec int `json:"hec"`
|
||||||
|
Maxrate uint64 `json:"maxrate"`
|
||||||
|
Nitro bool `json:"nitro"`
|
||||||
|
Phyr bool `json:"phyr"`
|
||||||
|
Rate uint64 `json:"rate"`
|
||||||
|
RtxC int `json:"rtx_c,omitempty"`
|
||||||
|
RtxTx int `json:"rtx_tx,omitempty"`
|
||||||
|
RtxUc int `json:"rtx_uc,omitempty"`
|
||||||
|
Rxmt int `json:"rxmt"`
|
||||||
|
RxmtCorr int `json:"rxmt_corr"`
|
||||||
|
RxmtUncorr int `json:"rxmt_uncorr"`
|
||||||
|
Ses int `json:"ses"`
|
||||||
|
Snr int `json:"snr"`
|
||||||
|
Snr10 int `json:"snr_10"`
|
||||||
|
} `json:"up"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type database struct {
|
||||||
|
DB string `json:"db"`
|
||||||
|
DateStart int `json:"date_start,omitempty"`
|
||||||
|
DateEnd int `json:"date_end,omitempty"`
|
||||||
|
Precision int `json:"precision,omitempty"`
|
||||||
|
Fields []string `json:"fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://dev.freebox.fr/sdk/os/freeplug/
|
||||||
|
type freeplug struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Result []freeplugNetwork `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type freeplugNetwork struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Members []freeplugMember `json:"members"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type freeplugMember struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Local bool `json:"local"`
|
||||||
|
NetRole string `json:"net_role"`
|
||||||
|
EthPortStatus string `json:"eth_port_status"`
|
||||||
|
EthFullDuplex bool `json:"eth_full_duplex"`
|
||||||
|
HasNetwork bool `json:"has_network"`
|
||||||
|
EthSpeed int `json:"eth_speed"`
|
||||||
|
Inative int `json:"inactive"`
|
||||||
|
NetID string `json:"net_id"`
|
||||||
|
RxRate int64 `json:"rx_rate"`
|
||||||
|
TxRate int64 `json:"tx_rate"`
|
||||||
|
Model string `json:"model"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://dev.freebox.fr/sdk/os/lan/
|
||||||
|
type l3c struct {
|
||||||
|
Addr string `json:"addr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type lanHost struct {
|
||||||
|
Reachable bool `json:"reachable,omitempty"`
|
||||||
|
PrimaryName string `json:"primary_name,omitempty"`
|
||||||
|
Vendor_name string `json:"vendor_name,omitempty"`
|
||||||
|
L3c []l3c `json:"l3connectivities,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type lan struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Result []lanHost `json:"result"`
|
||||||
|
ErrorCode string `json:"error_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type idNameValue struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Value int `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://dev.freebox.fr/sdk/os/system/
|
||||||
|
type system struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Result struct {
|
||||||
|
Mac string `json:"mac,omitempty"`
|
||||||
|
FanRPM int `json:"fan_rpm,omitempty"`
|
||||||
|
BoxFlavor string `json:"box_flavor,omitempty"`
|
||||||
|
TempCpub int `json:"temp_cpub,omitempty"`
|
||||||
|
TempCpum int `json:"temp_cpum,omitempty"`
|
||||||
|
DiskStatus string `json:"disk_status,omitempty"`
|
||||||
|
TempHDD int `json:"temp_hdd,omitempty"`
|
||||||
|
BoardName string `json:"board_name,omitempty"`
|
||||||
|
TempSW int `json:"temp_sw,omitempty"`
|
||||||
|
Uptime string `json:"uptime,omitempty"`
|
||||||
|
UptimeVal int `json:"uptime_val,omitempty"`
|
||||||
|
UserMainStorage string `json:"user_main_storage,omitempty"`
|
||||||
|
BoxAuthenticated bool `json:"box_authenticated,omitempty"`
|
||||||
|
Serial string `json:"serial,omitempty"`
|
||||||
|
FirmwareVersion string `json:"firmware_version,omitempty"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://dev.freebox.fr/sdk/os/wifi/
|
||||||
|
type wifiAccessPoint struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
ID int `json:"id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wifi struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Result []wifiAccessPoint `json:"result,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wifiStation struct {
|
||||||
|
Hostname string `json:"hostname,omitempty"`
|
||||||
|
MAC string `json:"mac,omitempty"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
Inactive int `json:"inactive,omitempty"`
|
||||||
|
RXBytes int64 `json:"rx_bytes,omitempty"`
|
||||||
|
TXBytes int64 `json:"tx_bytes,omitempty"`
|
||||||
|
ConnectionDuration int `json:"conn_duration,omitempty"`
|
||||||
|
TXRate int64 `json:"tx_rate,omitempty"`
|
||||||
|
RXRate int64 `json:"rx_rate,omitempty"`
|
||||||
|
Signal int `json:"signal,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wifiStations struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Result []wifiStation `json:"result,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type app struct {
|
||||||
|
AppID string `json:"app_id"`
|
||||||
|
AppName string `json:"app_name"`
|
||||||
|
AppVersion string `json:"app_version"`
|
||||||
|
DeviceName string `json:"device_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type api struct {
|
||||||
|
authz string
|
||||||
|
login string
|
||||||
|
loginSession string
|
||||||
|
}
|
||||||
|
|
||||||
|
type store struct {
|
||||||
|
location string
|
||||||
|
}
|
||||||
|
|
||||||
|
type authInfo struct {
|
||||||
|
myApp app
|
||||||
|
myAPI api
|
||||||
|
myStore store
|
||||||
|
myReader *bufio.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
type postRequest struct {
|
||||||
|
method, url, header string
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://dev.freebox.fr/sdk/os/vpn/
|
||||||
|
type vpnServer struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Result []struct {
|
||||||
|
RxBytes int64 `json:"rx_bytes,omitempty"`
|
||||||
|
Authenticated bool `json:"authenticated,omitempty"`
|
||||||
|
TxBytes int64 `json:"tx_bytes,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Vpn string `json:"vpn,omitempty"`
|
||||||
|
SrcIP string `json:"src_ip,omitempty"`
|
||||||
|
AuthTime int32 `json:"auth_time,omitempty"`
|
||||||
|
LocalIP string `json:"local_ip,omitempty"`
|
||||||
|
} `json:"result,omitempty"`
|
||||||
|
}
|
Reference in New Issue
Block a user