Initial Commit

This commit is contained in:
David Newhall II 2020-06-22 22:54:56 -07:00 committed by davidnewhall2
commit d9df2293a5
11 changed files with 1053 additions and 0 deletions

View File

@ -0,0 +1,9 @@
language: go
go:
- 1.14.x
before_install:
# download super-linter: golangci-lint
- curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin latest
script:
- go test ./...
- golangci-lint run --enable-all

21
core/webserver/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 David Newhall II
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

72
core/webserver/README.md Normal file
View File

@ -0,0 +1,72 @@
# webserver
Built-In Web Server Go Library for UniFi Poller. **INCOMPLETE**
Enabling the web server is optional. It provides a window into the running data.
The web server may be secured with a simple password. SSL is also optional.
## Interface
- Recent logs from poller are visible.
- Uptime and Version are displayed across the top.
### Controllers
- The web server interface allows you to see the configuration for each controller.
- You may select a controller, and then select a site on the controller.
- Some meta data about each controller is displayed, such as sites, clients and devices.
- Example config: [up.json.example](https://github.com/unifi-poller/unifi-poller/blob/master/examples/up.json.example)
### Input Plugins
- You may view input plugin configuration. Currently only UniFi.
- The example config above shows input plugin data.
### Output Plugins
- You may view output plugin configuration. Currently Prometheus and InfluxDB.
- The example config above shows output plugin data.
### Sites
Each controller has 1 or more sites. Most people only have 1, but some enterprises
run this software and have many more. Each site has devices like switches (`USW`), access
points (`UAP`) and routers (`USG`/`UDM`). We'll have counts for each device type. Each device has a name.
Each device has a count of clients (access points have clients). We'll want to expose
this, but it's not in a useful format yet. It'll look something like what you see below,
but keep the visualization expandable. We may add "model" and "serial number" for each device.
There is a handful of meta data per device. Some users have hundreds of devices.
```
{
"site_name_here": {
"clients": 22,
"UAP": [
{
"name": "ap1-room",
"clients": 6
},
{
"name": "ap2-bran",
"clients": 6
}
],
"USW": [
{
"name": "sw1-cube",
"model": "US-500w-P",
"serial": "xyz637sjs999",
"clients": 7
},
{
"name": "sw2-trap",
"clients": 3
}
],
"USG": [
{
"name": "gw1-role",
"clients": 22
}
]
}
}

13
core/webserver/go.mod Normal file
View File

@ -0,0 +1,13 @@
module github.com/unifi-poller/webserver
go 1.14
require (
github.com/gorilla/mux v1.7.4
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.7.1 // indirect
github.com/unifi-poller/poller v0.0.8-0.20200628131550-26430cac16c1
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
google.golang.org/protobuf v1.25.0 // indirect
)

180
core/webserver/go.sum Normal file
View File

@ -0,0 +1,180 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A=
github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.1.1 h1:/ZKcW+ixpq2dOl4yeH4qvACNXnkiDCp5e/F5Tq07X7o=
github.com/prometheus/procfs v0.1.1/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/unifi-poller/poller v0.0.8-0.20200625051244-75cdbd34e5d4/go.mod h1:pJ/MeYaakLOOpbyc7s4zeZ92UzNK/rir5jkA7t5jIjo=
github.com/unifi-poller/poller v0.0.8-0.20200626082958-a9a7092a5684 h1:r1B8GoI47czgGnQ7WY89qlSKqSE1d1pQmcLfdXVW/+Y=
github.com/unifi-poller/poller v0.0.8-0.20200626082958-a9a7092a5684/go.mod h1:pJ/MeYaakLOOpbyc7s4zeZ92UzNK/rir5jkA7t5jIjo=
github.com/unifi-poller/poller v0.0.8-0.20200628131550-26430cac16c1 h1:SHKYtAu4yB5bVhMuRkoHr8Ss1Ffu5dKLJ13rGWufLFI=
github.com/unifi-poller/poller v0.0.8-0.20200628131550-26430cac16c1/go.mod h1:fObadG7weiVnSpFu8pFpGfo2bYYFc7hUMe770FovSc8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38=
golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golift.io/cnfg v0.0.5 h1:HnMU8Z9C/igKvir1dqaHx5BPuNGZrp99FCtdJyP2Z4I=
golift.io/cnfg v0.0.5/go.mod h1:ScFDIJg/rJGHbRaed/i7g1lBhywEjB0JiP2uZr3xC3A=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

106
core/webserver/handlers.go Normal file
View File

@ -0,0 +1,106 @@
package webserver
import (
"net/http"
"path/filepath"
"github.com/gorilla/mux"
)
/* This file has the methods that pass out actual content. */
// Returns the main index file.
// If index.html becomes a template, this is where it can be compiled.
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
index := filepath.Join(s.HTMLPath, "index.html")
http.ServeFile(w, r, index)
}
// Arbitrary /health handler.
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
s.handleDone(w, []byte("OK"), mimeHTML)
}
// Returns static files from static-files path. /css, /js, /img (/images, /image).
func (s *Server) handleStatic(w http.ResponseWriter, r *http.Request) {
switch v := mux.Vars(r)["sub"]; v {
case "image", "img":
dir := http.Dir(filepath.Join(s.HTMLPath, "static", "images"))
http.StripPrefix("/"+v, http.FileServer(dir)).ServeHTTP(w, r)
default: // images, js, css, etc
dir := http.Dir(filepath.Join(s.HTMLPath, "static", v))
http.StripPrefix("/"+v, http.FileServer(dir)).ServeHTTP(w, r)
}
}
// Returns web server and poller configs. /api/v1/config.
func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"poller": s.Collect.Poller()}
s.handleJSON(w, data)
}
// Returns a list of input and output plugins: /api/v1/plugins.
func (s *Server) handlePlugins(w http.ResponseWriter, r *http.Request) {
data := map[string][]string{"inputs": s.Collect.Inputs(), "outputs": s.Collect.Outputs()}
s.handleJSON(w, data)
}
// Returns an output plugin's data: /api/v1/output/{output}.
func (s *Server) handleOutput(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
c := s.plugins.getOutput(vars["output"])
if c == nil {
s.handleMissing(w, r)
return
}
c.RLock()
defer c.RUnlock()
switch value := vars["value"]; vars["sub"] {
default:
s.handleJSON(w, c.Config)
case "events":
s.handleJSON(w, c.Events)
case "counters":
if value == "" {
s.handleJSON(w, c.Counter)
} else {
s.handleJSON(w, map[string]int64{value: c.Counter[value]})
}
}
}
// Returns an input plugin's data: /api/v1/input/{input}.
func (s *Server) handleInput(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
c := s.plugins.getInput(vars["input"])
if c == nil {
s.handleMissing(w, r)
return
}
c.RLock()
defer c.RUnlock()
switch value := vars["value"]; vars["sub"] {
default:
s.handleJSON(w, c.Config)
case "events":
s.handleJSON(w, c.Events)
case "sites":
s.handleJSON(w, c.Sites)
case "devices":
s.handleJSON(w, c.Devices)
case "clients":
s.handleJSON(w, c.Clients)
case "counters":
if value != "" {
s.handleJSON(w, map[string]int64{value: c.Counter[value]})
} else {
s.handleJSON(w, c.Counter)
}
}
}

36
core/webserver/logger.go Normal file
View File

@ -0,0 +1,36 @@
package webserver
import (
"fmt"
"time"
)
// Logf logs a message.
func (s *Server) Logf(msg string, v ...interface{}) {
NewOutputEvent(PluginName, PluginName, &Event{
Ts: time.Now(),
Msg: fmt.Sprintf(msg, v...),
Tags: map[string]string{"type": "info"},
})
s.Collect.Logf(msg, v...)
}
// LogErrorf logs an error message.
func (s *Server) LogErrorf(msg string, v ...interface{}) {
NewOutputEvent(PluginName, PluginName, &Event{
Ts: time.Now(),
Msg: fmt.Sprintf(msg, v...),
Tags: map[string]string{"type": "error"},
})
s.Collect.LogErrorf(msg, v...)
}
// LogDebugf logs a debug message.
func (s *Server) LogDebugf(msg string, v ...interface{}) {
NewOutputEvent(PluginName, PluginName, &Event{
Ts: time.Now(),
Msg: fmt.Sprintf(msg, v...),
Tags: map[string]string{"type": "debug"},
})
s.Collect.LogDebugf(msg, v...)
}

236
core/webserver/plugins.go Normal file
View File

@ -0,0 +1,236 @@
package webserver
import "sync"
type webPlugins struct {
*Config
inputs []*Input
outputs []*Output
sync.RWMutex // Locks both of the above slices.
}
// This is global so other plugins can call its methods.
var plugins = &webPlugins{} // nolint: gochecknoglobals
// UpdateInput allows an input plugin to create an entry or update an existing entry.
func UpdateInput(config *Input) {
if plugins.Enable {
plugins.updateInput(config)
}
}
// NewInputEvent adds an event for an input plugin.
func NewInputEvent(name, id string, event *Event) {
if plugins.Enable {
plugins.newInputEvent(name, id, event)
}
}
// NewOutputEvent adds an event for an output plugin.
func NewOutputEvent(name, id string, event *Event) {
if plugins.Enable {
plugins.newOutputEvent(name, id, event)
}
}
// UpdateOutput allows an output plugin to create an entry or update an existing entry.
func UpdateOutput(config *Output) {
if plugins.Enable {
plugins.updateOutput(config)
}
}
// UpdateOutputCounter allows an output plugin to update a counter's value.
func UpdateOutputCounter(plugin, label string, values ...int64) {
if plugins.Enable {
plugins.updateOutputCounter(plugin, label, values...)
}
}
// UpdateInputCounter allows an input plugin to update a counter's value.
// Set any arbitrary counter. These are displayed on the web interface.
func UpdateInputCounter(plugin, label string, values ...int64) {
if plugins.Enable {
plugins.updateInputCounter(plugin, label, values...)
}
}
func (w *webPlugins) updateOutputCounter(plugin, label string, values ...int64) {
if len(values) == 0 {
values = []int64{1}
}
output := w.getOutput(plugin)
if output == nil {
return
}
output.Lock()
defer output.Unlock()
if output.Counter == nil {
output.Counter = make(map[string]int64)
}
for _, v := range values {
output.Counter[label] += v
}
}
func (w *webPlugins) updateInputCounter(plugin, label string, values ...int64) {
if len(values) == 0 {
values = []int64{1}
}
input := w.getInput(plugin)
if input == nil {
return
}
input.Lock()
defer input.Unlock()
if input.Counter == nil {
input.Counter = make(map[string]int64)
}
for _, v := range values {
input.Counter[label] += v
}
}
func (w *webPlugins) updateInput(config *Input) {
if config == nil {
return
}
input := w.getInput(config.Name)
if input == nil {
w.newInput(config)
return
}
config.Lock()
defer config.Unlock()
if config.Clients != nil {
input.Clients = config.Clients
}
if config.Sites != nil {
input.Sites = config.Sites
}
if config.Devices != nil {
input.Devices = config.Devices
}
if config.Config != nil {
input.Config = config.Config
}
if config.Counter != nil {
input.Counter = config.Counter
}
}
func (w *webPlugins) updateOutput(config *Output) {
if config == nil || config.Config == nil {
return
}
output := w.getOutput(config.Name)
if output == nil {
w.newOutput(config)
return
}
config.Lock()
defer config.Unlock()
if config.Config != nil {
output.Config = config.Config
}
if config.Counter != nil {
output.Counter = config.Counter
}
}
func (w *webPlugins) newInputEvent(plugin, id string, event *Event) {
input := w.getInput(plugin)
if input == nil {
return
}
input.Lock()
defer input.Unlock()
if input.Events == nil {
input.Events = make(map[string]*Events)
}
if _, ok := input.Events[id]; !ok {
input.Events[id] = &Events{}
}
input.Events[id].add(event, int(w.Config.MaxEvents))
}
func (w *webPlugins) newOutputEvent(plugin, id string, event *Event) {
output := w.getOutput(plugin)
if output == nil {
return
}
output.Lock()
defer output.Unlock()
if output.Events == nil {
output.Events = make(map[string]*Events)
}
if _, ok := output.Events[id]; !ok {
output.Events[id] = &Events{}
}
output.Events[id].add(event, int(w.Config.MaxEvents))
}
func (w *webPlugins) newInput(config *Input) {
w.Lock()
defer w.Unlock()
w.inputs = append(w.inputs, config)
}
func (w *webPlugins) newOutput(config *Output) {
w.Lock()
defer w.Unlock()
w.outputs = append(w.outputs, config)
}
func (w *webPlugins) getInput(name string) *Input {
w.RLock()
defer w.RUnlock()
for i := range w.inputs {
if w.inputs[i].Name == name {
return w.inputs[i]
}
}
return nil
}
func (w *webPlugins) getOutput(name string) *Output {
w.RLock()
defer w.RUnlock()
for i := range w.outputs {
if w.outputs[i].Name == name {
return w.outputs[i]
}
}
return nil
}

View File

@ -0,0 +1,107 @@
package webserver
import (
"sync"
"time"
)
// Input is the data tracked for intput plugins.
// An input plugin should fill this data every time it polls this data.
// Partial update are OK. Set non-updated fields to nil and they're ignored.
type Input struct {
Name string
Sites Sites
Events map[string]*Events
Devices Devices
Clients Clients
Config interface{}
Counter map[string]int64
sync.RWMutex // Locks this data structure.
}
// Output is the data tracked for output plugins.
// Output plugins should fill this data on startup,
// and regularly update counters for things worth counting.
// Setting Config will overwrite previous value.
type Output struct {
Name string
Events map[string]*Events
Config interface{}
Counter map[string]int64
sync.RWMutex // Locks this data structure.
}
/*
These are minimal types to display a small set of data on the web interface.
These may be expanded upon, in time, as users express their needs and wants.
*/
// Sites is a list of network locations.
type Sites []*Site
// Site is a network location and its meta data.
type Site struct {
ID string `json:"id"`
Name string `json:"name"`
Desc string `json:"desc"`
Source string `json:"source"`
}
// Events allows each plugin to have a map of events. ie. one map per controller.
type Events struct {
Latest time.Time `json:"latest"`
Events []*Event `json:"events"`
}
// Event is like a log message.
type Event struct {
Ts time.Time `json:"ts"`
Msg string `json:"msg"`
Tags map[string]string `json:"tags,omitempty"`
}
// add adds a new event and makes sure the slice is not too big.
func (e *Events) add(event *Event, max int) {
if !e.Latest.Before(event.Ts) {
return // Ignore older events.
}
e.Latest = event.Ts
e.Events = append(e.Events, event)
if i := len(e.Events) - max; i > 0 {
e.Events = e.Events[i:]
}
}
// Devices is a list of network devices and their data.
type Devices []*Device
// Device holds the data for a network device.
type Device struct {
Name string `json:"name"`
SiteID string `json:"site_id"`
Source string `json:"source"`
MAC string `json:"mac"`
IP string `json:"ip"`
Type string `json:"type"`
Model string `json:"model"`
Version string `json:"version"`
Config interface{} `json:"config,omitempty"`
}
// Clients is a list of clients with their data.
type Clients []*Client
// Client holds the data for a network client.
type Client struct {
Name string `json:"name"`
SiteID string `json:"site_id"`
Source string `json:"source"`
MAC string `json:"mac"`
IP string `json:"ip"`
Type string `json:"type"`
DeviceMAC string `json:"device_mac"`
Since time.Time `json:"since"`
Last time.Time `json:"last"`
}

139
core/webserver/server.go Normal file
View File

@ -0,0 +1,139 @@
// Package webserver is a UniFi Poller plugin that exports running data to a web interface.
package webserver
import (
"net/http"
"os"
"path/filepath"
"strconv"
"time"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/unifi-poller/poller"
"golang.org/x/crypto/bcrypt"
)
const (
// PluginName identifies this output plugin.
PluginName = "WebServer"
// DefaultPort is the default web http port.
DefaultPort = 37288
// DefaultEvents is the default number of events stored per plugin.
DefaultEvents = 200
)
// Config is the webserver library input config.
type Config struct {
Enable bool `json:"enable" toml:"enable" xml:"enable,attr" yaml:"enable"`
SSLCrtPath string `json:"ssl_cert_path" toml:"ssl_cert_path" xml:"ssl_cert_path" yaml:"ssl_cert_path"`
SSLKeyPath string `json:"ssl_key_path" toml:"ssl_key_path" xml:"ssl_key_path" yaml:"ssl_key_path"`
Port uint `json:"port" toml:"port" xml:"port" yaml:"port"`
Accounts accounts `json:"accounts" toml:"accounts" xml:"accounts" yaml:"accounts"`
HTMLPath string `json:"html_path" toml:"html_path" xml:"html_path" yaml:"html_path"`
MaxEvents uint `json:"max_events" toml:"max_events" xml:"max_events" yaml:"max_events"`
}
// accounts stores a map of usernames and password hashes.
type accounts map[string]string
// Server is the main library struct/data.
type Server struct {
*Config `json:"webserver" toml:"webserver" xml:"webserver" yaml:"webserver"`
server *http.Server
plugins *webPlugins
Collect poller.Collect
}
// init is how this modular code is initialized by the main app.
// This module adds itself as an output module to the poller core.
func init() { // nolint: gochecknoinits
s := &Server{plugins: plugins, Config: &Config{
Port: DefaultPort,
HTMLPath: filepath.Join(poller.DefaultObjPath, "web"),
MaxEvents: DefaultEvents,
}}
plugins.Config = s.Config
poller.NewOutput(&poller.Output{
Name: PluginName,
Config: s,
Method: s.Run,
})
}
// Run starts the server and gets things going.
func (s *Server) Run(c poller.Collect) error {
if s.Collect = c; s.Config == nil || s.Port == 0 || s.HTMLPath == "" || !s.Enable {
s.Logf("Web server disabled!")
return nil
}
if _, err := os.Stat(s.HTMLPath); err != nil {
return errors.Wrap(err, "problem with HTML path")
}
UpdateOutput(&Output{Name: PluginName, Config: s.Config})
return s.Start()
}
// Start gets the web server going.
func (s *Server) Start() (err error) {
s.server = &http.Server{
Addr: "0.0.0.0:" + strconv.Itoa(int(s.Port)),
WriteTimeout: time.Minute,
ReadTimeout: time.Minute,
IdleTimeout: time.Minute,
Handler: s.newRouter(), // *mux.Router
}
if s.SSLCrtPath == "" || s.SSLKeyPath == "" {
s.Logf("Web server starting without SSL. Listening on HTTP port %d", s.Port)
err = s.server.ListenAndServe()
} else {
s.Logf("Web server starting with SSL. Listening on HTTPS port %d", s.Port)
err = s.server.ListenAndServeTLS(s.SSLCrtPath, s.SSLKeyPath)
}
if errors.Is(err, http.ErrServerClosed) {
return nil
}
return err
}
func (s *Server) newRouter() *mux.Router {
router := mux.NewRouter()
// special routes
//router.Handle("/debug/vars", http.DefaultServeMux).Methods("GET") // unauthenticated expvar
router.HandleFunc("/health", s.handleLog(s.handleHealth)).Methods("GET") // unauthenticated health
// main web app/files/js/css
router.HandleFunc("/", s.basicAuth(s.handleIndex)).Methods("GET", "POST")
router.PathPrefix("/{sub:css|js|img|image|images}/").Handler((s.basicAuth(s.handleStatic))).Methods("GET")
// api paths for json dumps
router.HandleFunc("/api/v1/config", s.basicAuth(s.handleConfig)).Methods("GET")
router.HandleFunc("/api/v1/plugins", s.basicAuth(s.handlePlugins)).Methods("GET")
router.HandleFunc("/api/v1/input/{input}", s.basicAuth(s.handleInput)).Methods("GET")
router.HandleFunc("/api/v1/input/{input}/{sub}", s.basicAuth(s.handleInput)).Methods("GET")
router.HandleFunc("/api/v1/input/{input}/{sub}/{value}", s.basicAuth(s.handleInput)).Methods("GET")
router.HandleFunc("/api/v1/output/{output}", s.basicAuth(s.handleOutput)).Methods("GET")
router.HandleFunc("/api/v1/output/{output}/{sub}", s.basicAuth(s.handleOutput)).Methods("GET")
router.HandleFunc("/api/v1/output/{output}/{sub}/{value}", s.basicAuth(s.handleOutput)).Methods("GET")
router.PathPrefix("/").Handler(s.basicAuth(s.handleMissing)).Methods("GET", "POST", "PUT") // 404 everything.
return router
}
// PasswordIsCorrect returns true if the provided password matches a user's account.
func (a accounts) PasswordIsCorrect(user, pass string, ok bool) bool {
if len(a) == 0 { // If true then no accounts defined in config; allow anyone.
return true
} else if !ok { // If true then r.BasicAuth() failed, not a valid user.
return false
} else if user, ok = a[user]; !ok { // The user var is now the password hash.
return false
}
return bcrypt.CompareHashAndPassword([]byte(user), []byte(pass)) == nil
}

134
core/webserver/shared.go Normal file
View File

@ -0,0 +1,134 @@
package webserver
import (
"encoding/json"
"net"
"net/http"
"time"
"github.com/unifi-poller/poller"
)
/* This file has the methods that help the content-methods. Shared helpers. */
const (
xPollerError = "X-Poller-Error"
mimeJSON = "application/json"
mimeHTML = "text/plain; charset=utf-8"
)
// basicAuth wraps web requests with simple auth (and logging).
// Called on nearly every request.
func (s *Server) basicAuth(handler http.HandlerFunc) http.HandlerFunc {
return s.handleLog(func(w http.ResponseWriter, r *http.Request) {
if s.Accounts.PasswordIsCorrect(r.BasicAuth()) {
handler(w, r)
return
}
w.Header().Set("WWW-Authenticate", `Basic realm="Enter Name and Password to Login!"`)
w.WriteHeader(http.StatusUnauthorized)
})
}
// handleLog writes an Apache-like log line. Called on every request.
func (s *Server) handleLog(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Scheme = "https"; r.TLS == nil {
r.URL.Scheme = "http" // Set schema early in case another handler uses it.
}
// Use custom ResponseWriter to catch and log response data.
response := &ResponseWriter{Writer: w, Start: time.Now()}
handler(response, r) // Run provided handler with custom ResponseWriter.
user, _, _ := r.BasicAuth()
if user == "" {
user = "-" // Only used for logs.
}
logf := s.Logf // Standard log.
if response.Error != "" {
logf = s.LogErrorf // Format an error log.
response.Error = ` "` + response.Error + `"`
}
remote, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
remote = r.RemoteAddr
}
logf(`%s %s %s [%v] "%s %s://%s%s %s" %d %d "%s" "%s" %v%s`, remote, poller.AppName,
user, response.Start.Format("01/02/2006:15:04:05 -07:00"), r.Method, r.URL.Scheme,
r.Host, r.RequestURI, r.Proto, response.Code, response.Size, r.Referer(),
r.UserAgent(), time.Since(response.Start).Round(time.Microsecond), response.Error)
}
}
// handleMissing returns a blank 404.
func (s *Server) handleMissing(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", mimeHTML)
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte("404 page not found\n"))
}
// handleError is a pass-off function when a request returns an error.
func (s *Server) handleError(w http.ResponseWriter, err error) {
w.Header().Set("Content-Type", mimeHTML)
w.Header().Set(xPollerError, err.Error()) // signal
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error() + "\n"))
}
// handleDone is a pass-off function to finish a request.
func (s *Server) handleDone(w http.ResponseWriter, b []byte, cType string) {
w.Header().Set("Content-Type", cType)
w.WriteHeader(http.StatusOK)
_, _ = w.Write(append(b, []byte("\n")...))
}
// handleJSON sends a json-formatted data reply.
func (s *Server) handleJSON(w http.ResponseWriter, data interface{}) {
b, err := json.Marshal(data)
if err != nil {
s.handleError(w, err)
return
}
s.handleDone(w, b, mimeJSON)
}
/* Custom http.ResponseWriter interface method and struct overrides. */
// ResponseWriter is used to override http.ResponseWriter in our http.FileServer.
// This allows us to catch and log the response code, size and error; maybe others.
type ResponseWriter struct {
Code int
Size int
Error string
Start time.Time
Writer http.ResponseWriter
}
// Header sends a header to a client. Satisfies http.ResponseWriter interface.
func (w *ResponseWriter) Header() http.Header {
return w.Writer.Header()
}
// Write sends bytes to the client. Satisfies http.ResponseWriter interface.
// This also adds the written byte count to our size total.
func (w *ResponseWriter) Write(b []byte) (int, error) {
size, err := w.Writer.Write(b)
w.Size += size
return size, err
}
// WriteHeader sends an http StatusCode to a client. Satisfies http.ResponseWriter interface.
// This custom override method also saves the status code, and any error message (for logs).
func (w *ResponseWriter) WriteHeader(code int) {
w.Error = w.Header().Get(xPollerError) // Catch and save any response error.
w.Header().Del(xPollerError) // Delete the temporary signal header.
w.Code = code // Save the status code.
w.Writer.WriteHeader(code) // Pass the request through.
}