Compare commits

..

20 Commits

Author SHA1 Message Date
abc8ac0a7b Add bcrypt support for stats password 2023-05-10 20:57:38 -04:00
8ce11fa824 Fix initialization of conf for stats 2023-05-09 22:05:03 -04:00
mickkael
7001fa4fa5
Create arm-registry.yml (#58) 2023-04-11 13:51:41 +08:00
Devin Buhl
fa28701928
feat: add env configuration support (#57) 2023-04-11 13:51:29 +08:00
adamus1red
4afbe82283
Add docker multi-arch build (#55)
Add buildx & qemu for generating the multi-arch digest
Add builds for platforms amd64, arm64, Armv7 (Raspberry Pi 32bit), and 386
2023-04-11 13:50:59 +08:00
Tim Small
a3eec03845
Bump go-systemd from unreleased git version to release v22.4.0 (#54) 2023-04-11 13:50:39 +08:00
Sharl.Jimh.Tsin
2ea9df0aba
fix cookies not match while using subPath (#51) 2023-04-11 13:50:20 +08:00
minus
8e31fe25ac
Make Docker image easier to use (#50)
* Use single server example as default frontend

This just works out of the box

* Refactor Dockerfile

- Remove the asset dir since it's embedded in the executable anyway
- Use the code in the current directory to build instead of downloading.
  Makes local testing very easy.
- Cache Go dependencies
- Run without root privileges. Note that using a BoltDB database
  requires mounting a volume with the correct permissions. The default
  database has thus been changed to memory so that starting the image
  just works.

* Automatically publish images
2022-07-26 23:30:23 +08:00
kayos
580fc08e0e
Fix wrong format type in Fatalf call (#48)
> `web/listener_linux.go:57:3: github.com/sirupsen/logrus.Fatalf format %s has arg len(listeners) of wrong type int`

for ints we use %d
2022-07-23 20:20:28 +08:00
Maddie Zhan
9cbc95a6db Fix Windows build
Fixes #44, closes #45
2022-07-09 01:03:33 +08:00
Maddie Zhan
fa749f3b45 goreleaser: don't upx compress Linux MIPS bianries 2022-07-09 00:54:04 +08:00
Nicolas Ledez
a5d18ef24c
Added url_base parameter to customize the root url and allow hosting with another application. (#41)
* Added `url_base` parameter and code to rewrite URL

* Manage redirect on stats login

Co-authored-by: Nicolas Ledez <github.public@ledez.net>
2022-07-09 00:44:49 +08:00
Marc
f09f2df2e3
Changed installation guide (#40)
- Added a notice about install the software using prebuilt binary packages.
- Added a notice about installing the software using my Ansible role.
2022-07-09 00:44:00 +08:00
yas-nyan
86763bd3aa
Feature: TLS and HTTP/2 (#39) 2022-03-27 02:47:16 +08:00
Maddie Zhan
22f21a270b Add missing go.mod dependency 2022-01-18 15:52:20 +08:00
kaustubh105
5944c3ca28
Rework session management (#37)
Co-authored-by: Kaustubh <kaustubh@rop.app>
2022-01-18 15:48:01 +08:00
Tim Small
c67b8ec91d
Support systemd socket activation (#35)
* Support systemd socket activation

If the process has been started with systemd socket activation
configured, then serve requests on the passed-in socket instead of
attempting to bind to an address.

* Add example systemd unit files.

Add example systemd unit files which make use of systemd's security
facilities, and also allow binding to port 80 whilst running as an
unpriviliged process (using systemd socket activation).
2022-01-18 15:47:32 +08:00
Sean McAvoy
281aff1725
Update Dockerfile (#36)
update alpine to 3.15 (same as golang:alpine)
2022-01-18 15:46:15 +08:00
Axel Gembe
158e37d3ae
Docker: Fix asset copy step to copy from the correct directory (#33)
Assets were moved from the `assets` folder to the `web/assets` folder in
commit 7204ae2e194465ec6c6c77fffd39274c39461df4 but the `Dockerfile` has
not been adjusted to copy from there.
2021-10-17 21:51:13 +08:00
Maddie Zhan
2583e5113a Use default html assets 2021-09-27 11:11:08 +08:00
28 changed files with 3041 additions and 1139 deletions

47
.github/workflows/arm-registry.yml vendored Normal file
View File

@ -0,0 +1,47 @@
name: Create and publish a Docker image with ARM
on:
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
uses: docker/build-push-action@v3
env:
CI: false
with:
context: .
push: true
platforms: linux/amd64,linux/arm64,linux/arm/v7
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

64
.github/workflows/docker-publish.yml vendored Normal file
View File

@ -0,0 +1,64 @@
name: Docker
on:
push:
branches: [ "master" ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: [ "master" ]
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7,linux/386
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: ${{ env.PLATFORMS }}

View File

@ -22,15 +22,10 @@ builds:
- amd64
- arm
- arm64
- mips
- mipsle
goarm:
- 5
- 6
- 7
gomips:
- hardfloat
- softfloat
ignore:
- goos: darwin
goarch: 386
@ -77,6 +72,8 @@ builds:
goos:
- linux
goarch:
- mips
- mipsle
- mips64
- mips64le
gomips:

View File

@ -1,20 +1,18 @@
FROM golang:alpine AS build_base
#ENV GOARCH arm64
#ENV GOARCH amd64
RUN apk add --no-cache git gcc ca-certificates libc-dev \
&& mkdir -p /go/src/github.com/librespeed/ \
&& cd /go/src/github.com/librespeed/ \
&& git clone https://github.com/librespeed/speedtest-go.git
WORKDIR /go/src/github.com/librespeed/speedtest-go
RUN go get ./ && go build -ldflags "-w -s" -trimpath -o speedtest main.go
FROM golang:1.18-alpine AS build_base
RUN apk add --no-cache git gcc ca-certificates libc-dev
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY ./ ./
RUN go build -ldflags "-w -s" -trimpath -o speedtest .
FROM alpine:3.9
RUN apk add ca-certificates
FROM alpine:3.16
RUN apk add --no-cache ca-certificates
WORKDIR /app
COPY --from=build_base /go/src/github.com/librespeed/speedtest-go/speedtest .
COPY --from=build_base /go/src/github.com/librespeed/speedtest-go/assets ./assets
COPY --from=build_base /go/src/github.com/librespeed/speedtest-go/settings.toml .
COPY --from=build_base /build/speedtest ./
COPY settings.toml ./
USER nobody
EXPOSE 8989
CMD ["./speedtest"]

View File

@ -34,6 +34,19 @@ Works with mobile versions too.
## Installation
### Install using prebuilt binaries
1. Download the appropriate binary file from the [releases](https://github.com/librespeed/speedtest-go/releases/) page.
2. Unzip the archive.
3. Make changes to the configuration.
4. Run the binary.
5. Optional: Setup a systemd service file.
### Use Ansible for automatic installation
You can use an Ansible role for installing speedtest-go easily. You can find the role on the [Ansible galaxy](https://galaxy.ansible.com/flymia/ansible_speedtest_go). There is a [separate repository](https://github.com/flymia/ansible-speedtest_go) for documentation about the Ansible role.
### Compile from source
You need Go 1.16+ to compile the binary. If you have an older version of Go and don't want to install the tarball
manually, you can install newer version of Go into your `GOPATH`:
@ -116,6 +129,14 @@ manually, you can install newer version of Go into your `GOPATH`:
# if you use `bolt` as database, set database_file to database file location
database_file="speedtest.db"
# TLS and HTTP/2 settings. TLS is required for HTTP/2
enable_tls=false
enable_http2=false
# if you use HTTP/2 or TLS, you need to prepare certificates and private keys
# tls_cert_file="cert.pem"
# tls_key_file="privkey.pem"
```
## Differences between Go and PHP implementation and caveats
@ -144,4 +165,4 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/lgpl>.
along with this program. If not, see <https://www.gnu.org/licenses/lgpl>.

View File

@ -8,6 +8,7 @@ import (
type Config struct {
BindAddress string `mapstructure:"bind_address"`
Port string `mapstructure:"listen_port"`
BaseURL string `mapstructure:"url_base"`
ProxyProtocolPort string `mapstructure:"proxyprotocol_port"`
ServerLat float64 `mapstructure:"server_lat"`
ServerLng float64 `mapstructure:"server_lng"`
@ -25,6 +26,11 @@ type Config struct {
DatabasePassword string `mapstructure:"database_password"`
DatabaseFile string `mapstructure:"database_file"`
EnableHTTP2 bool `mapstructure:"enable_http2"`
EnableTLS bool `mapstructure:"enable_tls"`
TLSCertFile string `mapstructure:"tls_cert_file"`
TLSKeyFile string `mapstructure:"tls_key_file"`
}
var (
@ -34,6 +40,7 @@ var (
func init() {
viper.SetDefault("listen_port", "8989")
viper.SetDefault("url_base", "")
viper.SetDefault("proxyprotocol_port", "0")
viper.SetDefault("download_chunks", 4)
viper.SetDefault("distance_unit", "K")
@ -44,6 +51,8 @@ func init() {
viper.SetDefault("database_hostname", "localhost")
viper.SetDefault("database_name", "speedtest")
viper.SetDefault("database_username", "postgres")
viper.SetDefault("enable_tls", false)
viper.SetDefault("enable_http2", false)
viper.SetConfigName("settings")
viper.AddConfigPath(".")
@ -54,14 +63,9 @@ func Load(configPath string) Config {
configFile = configPath
viper.SetConfigFile(configPath)
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
log.Warnf("No config file found in search paths, using default values")
} else {
log.Fatalf("Error reading config: %s", err)
}
}
viper.SetEnvPrefix("speedtest")
viper.AutomaticEnv()
viper.ReadInConfig()
if err := viper.Unmarshal(&conf); err != nil {
log.Fatalf("Error parsing config: %s", err)

22
go.mod
View File

@ -3,25 +3,23 @@ module github.com/librespeed/speedtest
go 1.16
require (
github.com/breml/rootcerts v0.1.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-chi/chi/v5 v5.0.4
github.com/breml/rootcerts v0.2.1
github.com/coreos/go-systemd/v22 v22.4.0
github.com/go-chi/chi/v5 v5.0.7
github.com/go-chi/cors v1.2.0
github.com/go-chi/render v1.0.1
github.com/go-sql-driver/mysql v1.6.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/lib/pq v1.10.3
github.com/mitchellh/mapstructure v1.4.2 // indirect
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.1
github.com/lib/pq v1.10.4
github.com/oklog/ulid/v2 v2.0.2
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pires/go-proxyproto v0.6.1
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/viper v1.8.1
github.com/spf13/afero v1.8.0 // indirect
github.com/spf13/viper v1.10.1
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26
go.etcd.io/bbolt v1.3.6
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect
golang.org/x/crypto v0.9.0
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
)

327
go.sum
View File

@ -3,6 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
@ -15,9 +16,18 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -26,7 +36,7 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@ -36,27 +46,53 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/breml/rootcerts v0.1.0 h1:jf/F4MHeXBi7FVAHrPq+/MBmPlGXTbUAyR977bZ0j4E=
github.com/breml/rootcerts v0.1.0/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
github.com/breml/rootcerts v0.2.1 h1:GZMVDXOs945764NFck0vtHSjktKYubOFM0kjf5HAuwc=
github.com/breml/rootcerts v0.2.1/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.4.0 h1:y9YHcjnjynCd/DVbg5j9L/33jQM3MxJlbj/zWskzfGU=
github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -66,14 +102,19 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.0.4 h1:5e494iHzsYBiyXQAHHuI4tyJS9M3V84OuX3ufIIGHFo=
github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
@ -81,9 +122,15 @@ github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
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-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
@ -91,6 +138,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@ -99,6 +147,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
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=
@ -116,6 +165,7 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -129,10 +179,12 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@ -142,104 +194,159 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
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.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
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/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/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/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
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.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
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/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/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.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
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/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/afero v1.8.0 h1:5MmtuhAgYeU6qpa7w7bP0dv6MBYuup0vekhSpSkoq60=
github.com/spf13/afero v1.8.0/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
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/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
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=
@ -249,6 +356,7 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26 h1:UFHFmFfixpmfRBcxuu+LA9l8MdURWVdVNUHxO5n1d2w=
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26/go.mod h1:IGhd0qMDsUa9acVjsbsT7bu3ktadtGOHI79+idTew/M=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -256,11 +364,12 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -268,16 +377,24 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -290,8 +407,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -315,10 +432,12 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/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-20190108225652-1e06a53dbb7e/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=
@ -326,9 +445,11 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -347,10 +468,17 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -362,7 +490,12 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
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=
@ -374,24 +507,33 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -411,16 +553,38 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 h1:7ZDGnxgHAMw7thfC5bEos0RDAccZKxioiWBhfIe+tvw=
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -429,8 +593,10 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -440,7 +606,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
@ -448,9 +613,9 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -483,8 +648,15 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -510,7 +682,17 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -553,12 +735,34 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -578,7 +782,15 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
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=
@ -591,15 +803,20 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
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-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c=
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
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.3/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.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -6,7 +6,10 @@ import (
"github.com/go-chi/render"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
"github.com/librespeed/speedtest/config"
"github.com/librespeed/speedtest/database"
"github.com/librespeed/speedtest/database/schema"
@ -18,6 +21,37 @@ type StatsData struct {
Data []schema.TelemetryData
}
var (
store *sessions.CookieStore
conf *config.Config
checkPassword func(password string) bool
)
func statsInitialize(c *config.Config) {
key := []byte(securecookie.GenerateRandomKey(32))
store = sessions.NewCookieStore(key)
store.Options = &sessions.Options{
Path: c.BaseURL + "/stats",
MaxAge: 3600 * 1, // 1 hour
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
}
conf = c
// Check if StatsPassword is a valid bcrypt hash
if _, err := bcrypt.Cost([]byte(c.StatsPassword)); err == nil {
log.Println("statistics_password is valid bcrypt hash")
checkPassword = func(password string) bool {
return nil == bcrypt.CompareHashAndPassword([]byte(c.StatsPassword), []byte(password))
}
} else {
checkPassword = func(password string) bool {
return password == c.StatsPassword
}
}
}
func Stats(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
t, err := template.New("template").Parse(htmlTemplate)
@ -27,8 +61,6 @@ func Stats(w http.ResponseWriter, r *http.Request) {
return
}
conf := config.LoadedConfig()
if conf.DatabaseType == "none" {
render.PlainText(w, r, "Statistics are disabled")
return
@ -42,16 +74,15 @@ func Stats(w http.ResponseWriter, r *http.Request) {
if !data.NoPassword {
op := r.FormValue("op")
c, _ := r.Cookie("logged")
session, _ := store.Get(r, "logged")
auth, ok := session.Values["authenticated"].(bool)
if c != nil && c.Value == "true" {
if auth && ok {
if op == "logout" {
cookie := &http.Cookie{
Name: "logged",
Value: "false",
}
http.SetCookie(w, cookie)
http.Redirect(w, r, "/stats", http.StatusTemporaryRedirect)
session.Values["authenticated"] = false
session.Options.MaxAge = -1
session.Save(r, w)
http.Redirect(w, r, conf.BaseURL+"/stats", http.StatusTemporaryRedirect)
} else {
data.LoggedIn = true
@ -78,14 +109,12 @@ func Stats(w http.ResponseWriter, r *http.Request) {
}
} else {
if op == "login" {
session, _ := store.Get(r, "logged")
password := r.FormValue("password")
if password == conf.StatsPassword {
cookie := &http.Cookie{
Name: "logged",
Value: "true",
}
http.SetCookie(w, cookie)
http.Redirect(w, r, "/stats", http.StatusTemporaryRedirect)
if checkPassword(password) {
session.Values["authenticated"] = true
session.Save(r, w)
http.Redirect(w, r, conf.BaseURL+"/stats", http.StatusTemporaryRedirect)
} else {
w.WriteHeader(http.StatusForbidden)
}

View File

@ -87,6 +87,7 @@ type IPInfoResponse struct {
}
func Initialize(c *config.Config) {
statsInitialize(c)
// changed to use Noto Sans instead of OpenSans, due to issue:
// https://github.com/golang/freetype/issues/8
fLight, err := freetype.ParseFont(fontLightBytes)

View File

@ -2,6 +2,8 @@
bind_address=""
# backend listen port
listen_port=8989
# change the base URL
# url_base="/librespeed"
# proxy protocol port, use 0 to disable
proxyprotocol_port=0
# Server location
@ -20,7 +22,7 @@ redact_ip_addresses=false
# database type for statistics data, currently supports: none, memory, bolt, mysql, postgresql
# if none is specified, no telemetry/stats will be recorded, and no result PNG will be generated
database_type="bolt"
database_type="memory"
database_hostname=""
database_name=""
database_username=""
@ -28,3 +30,11 @@ database_password=""
# if you use `bolt` as database, set database_file to database file location
database_file="speedtest.db"
# TLS and HTTP/2 settings. TLS is required for HTTP/2
enable_tls=false
enable_http2=false
# if you use HTTP/2 or TLS, you need to prepare certificates and private keys
# tls_cert_file="cert.pem"
# tls_key_file="privkey.pem"

28
systemd/README.md Normal file
View File

@ -0,0 +1,28 @@
# Example systemd unit files
To use these, first review the speedtest.* unit files, and then:
cp ../speedtest /usr/local/bin/
mkdir -p /usr/local/share/speedtest /usr/local/etc
cp -aR ../web/assets /usr/local/share/speedtest/assets
cp speedtest-settings.toml /usr/local/etc
cp speedtest.* /etc/systemd/system/
systemctl daemon-reload
If you wish to use the bolt database type:
# Create static system user and group
adduser --system --group --no-create-home --disabled-password speedtest
mkdir -p /usr/local/var/speedtest
touch /usr/local/var/speedtest/speedtest.db
chown speedtest. /usr/local/var/speedtest/speedtest.db
To start (and enable at boot-up):
systemctl enable --now speedtest.socket
speedtest-go should now be listening for http request on port 80 on the local
machine.
You will need to customise the html files e.g. edit
`/usr/local/share/speedtest/assets/index.html` to suit your site.

View File

@ -0,0 +1,31 @@
# bind address, use empty string to bind to all interfaces, or when using socket activation
#bind_address=""
# backend listen port. Set this to "" when using socket activation
listen_port=""
# proxy protocol port, use 0 to disable
proxyprotocol_port=0
# Server location
server_lat=50.82589
server_lng=-0.141391
# ipinfo.io API key, if applicable
ipinfo_api_key=""
# assets directory path, defaults to `assets` in the same directory
assets_path="/usr/local/share/speedtest/assets"
# password for logging into statistics page
statistics_password="PASSWORD"
# redact IP addresses
redact_ip_addresses=false
# database type for statistics data, currently supports: none, memory, bolt, mysql, postgresql
# if none is specified, no telemetry/stats will be recorded, and no result PNG will be generated
#database_type="bolt"
database_type="memory"
database_hostname=""
database_name=""
database_username=""
database_password=""
# if you use `bolt` as database, set database_file to database file location
database_file="/usr/local/var/speedtest/speedtest.db"

135
systemd/speedtest.service Normal file
View File

@ -0,0 +1,135 @@
# Systemd unit file for speedtest-go. The defaults below are suitable for
# running all configurations in a medium-security environment. See comments
# below for addtional caveats - particularly those labelled "IMPORTANT".
# You can edit this file, or alternatively you may prefer to use systemd's
# "override" mechanisms, to avoid editing this file e.g. using:
# systemctl edit speedtest.service
[Unit]
Description=Speedtest-go Server
After=syslog.target network.target
# Default to using socket activation (see accompanying socket unit file to
# configure the bind address etc.).
Requires=speedtest.socket
After=speedtest.socket
[Service]
Type=simple
# The paths to the installed binary and configuration file:
ExecStart=/usr/local/bin/speedtest -c /usr/local/etc/speedtest-settings.toml
#WorkingDirectory=/usr/local/share/speedtest
#Restart=always
#RestartSec=5
# IMPORTANT!
# If you use a database file (not server), then you will need to disable the
# DynamicUser setting, and manually create the UNIX user and group specified
# below, to ensure the file is accessible across multiple invocations of the
# service.
DynamicUser=true
# You may prefer to use a different user or group name on your system.
User=speedtest
Group=speedtest
# The following options will work for all configurations, but are not the
# most secure, so you are advised to customise them as described below:
# If NOT using socket activation, or if using socket activation AND
# connecting to an external database server (MySQL, postgres) via TCP:
RestrictAddressFamilies=AF_INET AF_INET6
# If connecting to an external database via unix domain sockets (MySQL
# default to this mode of operation):
RestrictAddressFamilies=AF_UNIX
# If using 'none', 'memory', or 'bolt' database types, and socket activation
# then the process will not need to bind to any new sockets, so we can remove
# the earlier AF_UNIX option again. In systemd versions before 249 this is
# the only way to say "Restrict the use of all address families":
RestrictAddressFamilies=AF_UNIX
RestrictAddressFamilies=~AF_UNIX
# ...in systemd version 249 and later, we can instead use the much clearer:
#RestrictAddressFamilies=none
# The following options are available (in systemd v247) to restrict the
# actions of the speedtest server for reasons of increased security.
# As a whole, the purpose of these are to provide an additional layer of
# security by mitigating any unknown security vulnerabilities which may exist
# in speedtest or in the libraries, tools and operating system components
# which it relies upon.
# IMPORTANT!
# The following line must be customised to your individual requirements.
# e.g. if using the 'bolt' in-process database type:
ReadWritePaths=/usr/local/var/speedtest
# Makes created files group-readable, but inaccessible by others
UMask=027
# Many of the following options are desribed in the systemd.resource-control(5)
# manual page.
# The following may be useful in your environment:
#IPAddressDeny=
#IPAddressAllow=
#IPAccounting=true
#IPIngressFilterPath=
#SocketBindAllow=
# If your system doesn't support all of the features below (e.g. because of
# the use of a version of systemd older than 247), you may need to comment-out
# some of the following lines.
# n.b. It may be possible to further restrict speedtest, but this is a good
# start, and will guard against many potential zero-day vulnerabilities.
# See the output of `systemd-analyze security speedtest.service` for further
# opportunities. Patches welcome!
CapabilityBoundingSet=
LockPersonality=true
MemoryDenyWriteExecute=true
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=true
PrivateUsers=true
ProtectSystem=strict
ProtectHome=yes
ProtectClock=true
ProtectControlGroups=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectProc=invisible
ProtectHostname=true
RemoveIPC=true
RestrictNamespaces=true
RestrictSUIDSGID=true
RestrictRealtime=true
SystemCallArchitectures=native
SystemCallFilter=@system-service
# Additionally, you may wish to use some of the systemd options documented in
# systemd.resource-control(5) to limit the CPU, memory, file-system I/O and
# network I/O that the speedtest server is permitted to consume according to
# the individual requirements of your installation.
#CPUQuota=25%
#MemoryMax=bytes
#MemorySwapMax=bytes
#TasksMax=N
#IOReadBandwidthMax=device bytes
#IOWriteBandwidthMax=device bytes
#IOReadIOPSMax=device IOPS, IOWriteIOPSMax=device IOPS
#IPAccounting=true
#IPAddressAllow=
[Install]
WantedBy=multi-user.target

11
systemd/speedtest.socket Normal file
View File

@ -0,0 +1,11 @@
# Socket listener systemd unit file for speedtest-go. See the
# systemd.socket(5) manual page for many more options.
[Unit]
Description=Speedtest Web Server (http port 80) Socket
[Socket]
ListenStream=80
Accept=no
[Install]
WantedBy=sockets.target

View File

@ -0,0 +1,490 @@
<!DOCTYPE html>
<html>
<head>
<link rel="shortcut icon" href="favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<meta charset="UTF-8" />
<script type="text/javascript" src="speedtest.js"></script>
<script type="text/javascript">
function I(i){return document.getElementById(i);}
//LIST OF TEST SERVERS. See documentation for details if needed
var SPEEDTEST_SERVERS=[
{ //this server doesn't actually exist, remove it
name:"Example Server 1", //user friendly name for the server
server:"//test1.mydomain.com/", //URL to the server. // at the beginning will be replaced with http:// or https:// automatically
dlURL:"backend/garbage.php", //path to download test on this server (garbage.php or replacement)
ulURL:"backend/empty.php", //path to upload test on this server (empty.php or replacement)
pingURL:"backend/empty.php", //path to ping/jitter test on this server (empty.php or replacement)
getIpURL:"backend/getIP.php" //path to getIP on this server (getIP.php or replacement)
},
{ //this server doesn't actually exist, remove it
name:"Example Server 2", //user friendly name for the server
server:"//test2.example.com/", //URL to the server. // at the beginning will be replaced with http:// or https:// automatically
dlURL:"garbage.php", //path to download test on this server (garbage.php or replacement)
ulURL:"empty.php", //path to upload test on this server (empty.php or replacement)
pingURL:"empty.php", //path to ping/jitter test on this server (empty.php or replacement)
getIpURL:"getIP.php" //path to getIP on this server (getIP.php or replacement)
}
//add other servers here, comma separated
];
//INITIALIZE SPEEDTEST
var s=new Speedtest(); //create speedtest object
s.setParameter("telemetry_level","basic"); //enable telemetry
//SERVER AUTO SELECTION
function initServers(){
var noServersAvailable=function(){
I("message").innerHTML="No servers available";
}
var runServerSelect=function(){
s.selectServer(function(server){
if(server!=null){ //at least 1 server is available
I("loading").className="hidden"; //hide loading message
//populate server list for manual selection
for(var i=0;i<SPEEDTEST_SERVERS.length;i++){
if(SPEEDTEST_SERVERS[i].pingT==-1) continue;
var option=document.createElement("option");
option.value=i;
option.textContent=SPEEDTEST_SERVERS[i].name;
if(SPEEDTEST_SERVERS[i]===server) option.selected=true;
I("server").appendChild(option);
}
//show test UI
I("testWrapper").className="visible";
initUI();
}else{ //no servers are available, the test cannot proceed
noServersAvailable();
}
});
}
if(typeof SPEEDTEST_SERVERS === "string"){
//need to fetch list of servers from specified URL
s.loadServerList(SPEEDTEST_SERVERS,function(servers){
if(servers==null){ //failed to load server list
noServersAvailable();
}else{ //server list loaded
SPEEDTEST_SERVERS=servers;
runServerSelect();
}
});
}else{
//hardcoded server list
s.addTestPoints(SPEEDTEST_SERVERS);
runServerSelect();
}
}
var meterBk=/Trident.*rv:(\d+\.\d+)/i.test(navigator.userAgent)?"#EAEAEA":"#80808040";
var dlColor="#6060AA",
ulColor="#616161";
var progColor=meterBk;
//CODE FOR GAUGES
function drawMeter(c,amount,bk,fg,progress,prog){
var ctx=c.getContext("2d");
var dp=window.devicePixelRatio||1;
var cw=c.clientWidth*dp, ch=c.clientHeight*dp;
var sizScale=ch*0.0055;
if(c.width==cw&&c.height==ch){
ctx.clearRect(0,0,cw,ch);
}else{
c.width=cw;
c.height=ch;
}
ctx.beginPath();
ctx.strokeStyle=bk;
ctx.lineWidth=12*sizScale;
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,Math.PI*0.1);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle=fg;
ctx.lineWidth=12*sizScale;
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,amount*Math.PI*1.2-Math.PI*1.1);
ctx.stroke();
if(typeof progress !== "undefined"){
ctx.fillStyle=prog;
ctx.fillRect(c.width*0.3,c.height-16*sizScale,c.width*0.4*progress,4*sizScale);
}
}
function mbpsToAmount(s){
return 1-(1/(Math.pow(1.3,Math.sqrt(s))));
}
function format(d){
d=Number(d);
if(d<10) return d.toFixed(2);
if(d<100) return d.toFixed(1);
return d.toFixed(0);
}
//UI CODE
var uiData=null;
function startStop(){
if(s.getState()==3){
//speedtest is running, abort
s.abort();
data=null;
I("startStopBtn").className="";
I("server").disabled=false;
initUI();
}else{
//test is not running, begin
I("startStopBtn").className="running";
I("shareArea").style.display="none";
I("server").disabled=true;
s.onupdate=function(data){
uiData=data;
};
s.onend=function(aborted){
I("startStopBtn").className="";
I("server").disabled=false;
updateUI(true);
if(!aborted){
//if testId is present, show sharing panel, otherwise do nothing
try{
var testId=uiData.testId;
if(testId!=null){
var shareURL=window.location.href.substring(0,window.location.href.lastIndexOf("/"))+"/results/?id="+testId;
I("resultsImg").src=shareURL;
I("resultsURL").value=shareURL;
I("testId").innerHTML=testId;
I("shareArea").style.display="";
}
}catch(e){}
}
};
s.start();
}
}
//this function reads the data sent back by the test and updates the UI
function updateUI(forced){
if(!forced&&s.getState()!=3) return;
if(uiData==null) return;
var status=uiData.testState;
I("ip").textContent=uiData.clientIp;
I("dlText").textContent=(status==1&&uiData.dlStatus==0)?"...":format(uiData.dlStatus);
drawMeter(I("dlMeter"),mbpsToAmount(Number(uiData.dlStatus*(status==1?oscillate():1))),meterBk,dlColor,Number(uiData.dlProgress),progColor);
I("ulText").textContent=(status==3&&uiData.ulStatus==0)?"...":format(uiData.ulStatus);
drawMeter(I("ulMeter"),mbpsToAmount(Number(uiData.ulStatus*(status==3?oscillate():1))),meterBk,ulColor,Number(uiData.ulProgress),progColor);
I("pingText").textContent=format(uiData.pingStatus);
I("jitText").textContent=format(uiData.jitterStatus);
}
function oscillate(){
return 1+0.02*Math.sin(Date.now()/100);
}
//update the UI every frame
window.requestAnimationFrame=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||(function(callback,element){setTimeout(callback,1000/60);});
function frame(){
requestAnimationFrame(frame);
updateUI();
}
frame(); //start frame loop
//function to (re)initialize UI
function initUI(){
drawMeter(I("dlMeter"),0,meterBk,dlColor,0);
drawMeter(I("ulMeter"),0,meterBk,ulColor,0);
I("dlText").textContent="";
I("ulText").textContent="";
I("pingText").textContent="";
I("jitText").textContent="";
I("ip").textContent="";
}
</script>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#loading{
background-color:#FFFFFF;
color:#404040;
text-align:center;
}
span.loadCircle{
display:inline-block;
width:2em;
height:2em;
vertical-align:middle;
background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAP1BMVEUAAAB2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZyFzwnAAAAFHRSTlMAEvRFvX406baecwbf0casimhSHyiwmqgAAADpSURBVHja7dbJbQMxAENRahnN5lkc//5rDRAkDeRgHszXgACJoKiIiIiIiIiIiIiIiIiIiIj4HHspsrpAVhdVVguzrA4OWc10WcEqpwKbnBo0OU1Q5NSpsoJFTgOecrrdEag85DRgktNqfoEdTjnd7hrEHMEJvmRUYJbTYk5Agy6nau6Abp5Cm7mDBtRdPi9gyKdU7w4p1fsLvyqs8hl4z9/w3n/Hmr9WoQ65lAU4d7lMYOz//QboRR5jBZibLMZdAR6O/Vfa1PlxNr3XdS3HzK/HVPRu/KnLs8iAOh993VpRRERERMT/fAN60wwWaVyWwAAAAABJRU5ErkJggg==');
background-size:2em 2em;
margin-right:0.5em;
animation: spin 0.6s linear infinite;
}
@keyframes spin{
0%{transform:rotate(0deg);}
100%{transform:rotate(359deg);}
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#serverArea{
margin-top:1em;
}
#server{
font-size:1em;
padding:0.2em;
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:16em;
height:12.5em;
position:relative;
box-sizing:border-box;
}
div.testArea2{
display:inline-block;
width:14em;
height:7em;
position:relative;
box-sizing:border-box;
text-align:center;
}
div.testArea div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.testArea2 div.testName{
display:block;
text-align:center;
font-size:1.4em;
}
div.testArea div.meterText{
position:absolute;
bottom:1.55em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
div.testArea2 div.meterText{
display:inline-block;
font-size:2.5em;
}
div.meterText:empty:before{
content:"0.00";
}
div.testArea div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testArea2 div.unit{
display:inline-block;
}
div.testArea canvas{
position:absolute;
top:0; left:0; width:100%; height:100%;
z-index:1;
}
div.testGroup{
display:block;
margin: 0 auto;
}
#shareArea{
width:95%;
max-width:40em;
margin:0 auto;
margin-top:2em;
}
#shareArea > *{
display:block;
width:100%;
height:auto;
margin: 0.25em 0;
}
#privacyPolicy{
position:fixed;
top:2em;
bottom:2em;
left:2em;
right:2em;
overflow-y:auto;
width:auto;
height:auto;
box-shadow:0 0 3em 1em #000000;
z-index:999999;
text-align:left;
background-color:#FFFFFF;
padding:1em;
}
a.privacy{
text-align:center;
font-size:0.8em;
color:#808080;
padding: 0 3em;
}
div.closePrivacyPolicy {
width: 100%;
text-align: center;
}
div.closePrivacyPolicy a.privacy {
padding: 1em 3em;
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
}
div.visible{
animation: fadeIn 0.4s;
display:block;
}
div.hidden{
animation: fadeOut 0.4s;
display:none;
}
@keyframes fadeIn{
0%{
opacity:0;
}
100%{
opacity:1;
}
}
@keyframes fadeOut{
0%{
display:block;
opacity:1;
}
100%{
display:block;
opacity:0;
}
}
</style>
<title>LibreSpeed Example</title>
</head>
<body onload="initServers()">
<h1>LibreSpeed Example</h1>
<div id="loading" class="visible">
<p id="message"><span class="loadCircle"></span>Selecting a server...</p>
</div>
<div id="testWrapper" class="hidden">
<div id="startStopBtn" onclick="startStop()"></div><br/>
<a class="privacy" href="#" onclick="I('privacyPolicy').style.display=''">Privacy</a>
<div id="serverArea">
Server: <select id="server" onchange="s.setSelectedServer(SPEEDTEST_SERVERS[this.value])"></select>
</div>
<div id="test">
<div class="testGroup">
<div class="testArea2">
<div class="testName">Ping</div>
<div id="pingText" class="meterText" style="color:#AA6060"></div>
<div class="unit">ms</div>
</div>
<div class="testArea2">
<div class="testName">Jitter</div>
<div id="jitText" class="meterText" style="color:#AA6060"></div>
<div class="unit">ms</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<canvas id="dlMeter" class="meter"></canvas>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<canvas id="ulMeter" class="meter"></canvas>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div id="ipArea">
<span id="ip"></span>
</div>
<div id="shareArea" style="display:none">
<h3>Share results</h3>
<p>Test ID: <span id="testId"></span></p>
<input type="text" value="" id="resultsURL" readonly="readonly" onclick="this.select();this.focus();this.select();document.execCommand('copy');alert('Link copied')"/>
<img src="" id="resultsImg" />
</div>
</div>
<a href="https://github.com/librespeed/speedtest">Source code</a>
</div>
<div id="privacyPolicy" style="display:none">
<h2>Privacy Policy</h2>
<p>This HTML5 Speedtest server is configured with telemetry enabled.</p>
<h4>What data we collect</h4>
<p>
At the end of the test, the following data is collected and stored:
<ul>
<li>Test ID</li>
<li>Time of testing</li>
<li>Test results (download and upload speed, ping and jitter)</li>
<li>IP address</li>
<li>ISP information</li>
<li>Approximate location (inferred from IP address, not GPS)</li>
<li>User agent and browser locale</li>
<li>Test log (contains no personal information)</li>
</ul>
</p>
<h4>How we use the data</h4>
<p>
Data collected through this service is used to:
<ul>
<li>Allow sharing of test results (sharable image for forums, etc.)</li>
<li>To improve the service offered to you (for instance, to detect problems on our side)</li>
</ul>
No personal information is disclosed to third parties.
</p>
<h4>Your consent</h4>
<p>
By starting the test, you consent to the terms of this privacy policy.
</p>
<h4>Data removal</h4>
<p>
If you want to have your information deleted, you need to provide either the ID of the test or your IP address. This is the only way to identify your data, without this information we won't be able to comply with your request.<br/><br/>
Contact this email address for all deletion requests: <a href="mailto:PUT@YOUR_EMAIL.HERE">TO BE FILLED BY DEVELOPER</a>.
</p>
<br/><br/>
<div class="closePrivacyPolicy">
<a class="privacy" href="#" onclick="I('privacyPolicy').style.display='none'">Close</a>
</div>
<br/>
</div>
</body>
</html>

View File

@ -0,0 +1,245 @@
<!DOCTYPE html>
<html>
<head>
<link rel="shortcut icon" href="favicon.ico">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<title>LibreSpeed Example</title>
<script type="text/javascript" src="speedtest.js"></script>
<script type="text/javascript">
//LIST OF TEST SERVERS. See documentation for details if needed
var SPEEDTEST_SERVERS=[
{ //this server doesn't actually exist, remove it
name:"Example Server 1", //user friendly name for the server
server:"//test1.mydomain.com/", //URL to the server. // at the beginning will be replaced with http:// or https:// automatically
dlURL:"backend/garbage.php", //path to download test on this server (garbage.php or replacement)
ulURL:"backend/empty.php", //path to upload test on this server (empty.php or replacement)
pingURL:"backend/empty.php", //path to ping/jitter test on this server (empty.php or replacement)
getIpURL:"backend/getIP.php" //path to getIP on this server (getIP.php or replacement)
},
{ //this server doesn't actually exist, remove it
name:"Example Server 2", //user friendly name for the server
server:"//test2.example.com/", //URL to the server. // at the beginning will be replaced with http:// or https:// automatically
dlURL:"garbage.php", //path to download test on this server (garbage.php or replacement)
ulURL:"empty.php", //path to upload test on this server (empty.php or replacement)
pingURL:"empty.php", //path to ping/jitter test on this server (empty.php or replacement)
getIpURL:"getIP.php" //path to getIP on this server (getIP.php or replacement)
}
//add other servers here, comma separated
];
//INITIALIZE SPEEDTEST
var s=new Speedtest(); //create speedtest object
s.onupdate=function(data){ //callback to update data in UI
I("ip").textContent=data.clientIp;
I("dlText").textContent=(data.testState==1&&data.dlStatus==0)?"...":data.dlStatus;
I("ulText").textContent=(data.testState==3&&data.ulStatus==0)?"...":data.ulStatus;
I("pingText").textContent=data.pingStatus;
I("jitText").textContent=data.jitterStatus;
}
s.onend=function(aborted){ //callback for test ended/aborted
I("startStopBtn").className=""; //show start button again
if(aborted){ //if the test was aborted, clear the UI and prepare for new test
initUI();
}
}
function selectServer(){ //called after loading server list
s.selectServer(function(server){ //run server selection. When the server has been selected, display it in the UI
if(server==null){
I("serverId").textContent="No servers available";
}else{
I("startStopBtn").style.display=""; //show start/stop button again
I("serverId").textContent=server.name; //show name of test server
}
});
}
function loadServers(){ //called when the page is fully loaded
I("startStopBtn").style.display="none"; //hide start/stop button during server selection
if(typeof SPEEDTEST_SERVERS === "string"){
//load servers from url
s.loadServerList(SPEEDTEST_SERVERS,function(servers){
//list loaded
SPEEDTEST_SERVERS=servers;
selectServer();
});
}else{
//hardcoded list of servers, already loaded
s.addTestPoints(SPEEDTEST_SERVERS);
selectServer();
}
}
function startStop(){ //start/stop button pressed
if(s.getState()==3){
//speedtest is running, abort
s.abort();
}else{
//test is not running, begin
s.start();
I("startStopBtn").className="running";
}
}
//function to (re)initialize UI
function initUI(){
I("dlText").textContent="";
I("ulText").textContent="";
I("pingText").textContent="";
I("jitText").textContent="";
I("ip").textContent="";
}
function I(id){return document.getElementById(id);}
</script>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:14em;
height:9em;
position:relative;
box-sizing:border-box;
}
div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.meterText{
position:absolute;
bottom:1.5em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
#dlText{
color:#6060AA;
}
#ulText{
color:#309030;
}
#pingText,#jitText{
color:#AA6060;
}
div.meterText:empty:before{
color:#505050 !important;
content:"0.00";
}
div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testGroup{
display:inline-block;
}
@media all and (max-width:65em){
body{
font-size:1.5vw;
}
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
div.testGroup{
display:block;
margin: 0 auto;
}
}
</style>
</head>
<body>
<h1>LibreSpeed Example</h1>
<div id="startStopBtn" onclick="startStop()"></div>
<div id="serverId">Selecting server...</div>
<div id="test">
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Ping</div>
<div id="pingText" class="meterText"></div>
<div class="unit">ms</div>
</div>
<div class="testArea">
<div class="testName">Jitter</div>
<div id="jitText" class="meterText"></div>
<div class="unit">ms</div>
</div>
</div>
<div id="ipArea">
IP Address: <span id="ip"></span>
</div>
</div>
<a href="https://github.com/librespeed/speedtest">Source code</a>
<script type="text/javascript">
initUI();
loadServers();
</script>
</body>
</html>

View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<meta charset="UTF-8" />
<head>
<title>LibreSpeed Example</title>
<link rel="shortcut icon" href="favicon.ico">
</head>
<script type="text/javascript" src="speedtest.js"></script>
<body>
<h1>LibreSpeed Example</h1>
<h4>IP Address</h4>
<p id="ip"></p>
<h4>Download</h4>
<p id="download"></p>
<h4>Upload</h4>
<p id="upload"></p>
<h4>Latency</h4>
<p id="ping"></p>
<script type="text/javascript">
var s=new Speedtest();
s.onupdate = function (data) { // when status is received, put the values in the appropriate fields
document.getElementById('download').textContent = data.dlStatus + ' Mbit/s'
document.getElementById('upload').textContent = data.ulStatus + ' Mbit/s'
document.getElementById('ping').textContent = data.pingStatus + ' ms, ' + data.jitterStatus + ' ms jitter'
document.getElementById('ip').textContent = data.clientIp
}
s.start(); // start the speedtest with default settings
</script>
<a href="https://github.com/librespeed/speedtest">Source code</a>
</body>
</html>

View File

@ -0,0 +1,256 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>LibreSpeed Example</title>
<link rel="shortcut icon" href="favicon.ico">
<style type="text/css">
html,
body {
margin: 0;
padding: 0;
border: none;
text-align: center;
}
#startBtn {
display: inline-block;
border: 0.15em solid #000000;
padding: 0.3em 0.5em;
margin: 0.6em;
color: #000000;
text-decoration: none;
}
#ip {
margin: 0.8em 0;
font-size: 1.2em;
}
#chart1Area,
#chart2Area {
width: 100%;
max-width: 30em;
height: 10em;
display: block;
margin: 0 auto;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.bundle.min.js"></script> <!--THIS LIBRARY SUCKS!-->
<script src="speedtest.js"></script>
<script type="text/javascript">
var s = null
function runTest() {
var chart1ctx = document.getElementById('chart1Area').getContext('2d')
var chart2ctx = document.getElementById('chart2Area').getContext('2d')
var dlDataset = {
label: 'Download',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(75,192,192,0.4)',
borderColor: 'rgba(75,192,192,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(75,192,192,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(75,192,192,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [0],
spanGaps: false
}
var ulDataset = {
label: 'Upload',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(192,192,75,0.4)',
borderColor: 'rgba(192,192,75,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(192,192,75,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(192,192,75,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [0],
spanGaps: false
}
var pingDataset = {
label: 'Ping',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(75,220,75,0.4)',
borderColor: 'rgba(75,220,75,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(75,220,75,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(75,220,75,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [],
spanGaps: false
}
var jitterDataset = {
label: 'Jitter',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(220,75,75,0.4)',
borderColor: 'rgba(220,75,75,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(220,75,75,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(220,75,75,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [],
spanGaps: false
}
var chart1Options = {
type: 'line',
data: {
datasets: [dlDataset, ulDataset]
},
options: {
responsive: true,
legend: {
position: 'bottom'
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
display: false
},
ticks: {
beginAtZero: true
}
}],
yAxes: [{
display: true,
scaleLabel: 'Speed',
ticks: {
beginAtZero: true
}
}]
}
}
}
var chart2Options = {
type: 'line',
data: {
datasets: [pingDataset, jitterDataset]
},
options: {
responsive: true,
legend: {
position: 'bottom'
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
display: false
},
ticks: {
beginAtZero: true
}
}],
yAxes: [{
display: true,
scaleLabel: 'Latency',
ticks: {
beginAtZero: true
}
}]
}
}
}
var chart1 = new Chart(chart1ctx, chart1Options)
var chart2 = new Chart(chart2ctx, chart2Options)
document.getElementById('startBtn').style.display = 'none'
document.getElementById('testArea').style.display = ''
document.getElementById('abortBtn').style.display = ''
s=new Speedtest();
s.onupdate = function (data) {
var status = data.testState
if (status === 1 && Number(data.dlStatus) > 0) {
for(var i=~~(20*Number(data.dlProgress));i<20;i++) chart1.data.datasets[0].data[i]=(Number(data.dlStatus))
chart1.data.labels[chart1.data.datasets[0].data.length - 1] = ''
chart1.update()
}
if (status === 3 && Number(data.ulStatus) > 0) {
for(var i=~~(20*Number(data.ulProgress));i<20;i++) chart1.data.datasets[1].data[i]=(Number(data.ulStatus))
chart1.data.labels[chart1.data.datasets[1].data.length - 1] = ''
chart1.update()
}
if (status === 2 && Number(data.pingStatus) > 0) {
chart2.data.datasets[0].data.push(Number(data.pingStatus))
chart2.data.datasets[1].data.push(Number(data.jitterStatus))
chart2.data.labels[chart2.data.datasets[0].data.length - 1] = ''
chart2.data.labels[chart2.data.datasets[1].data.length - 1] = ''
chart2.update()
}
ip.textContent = data.clientIp
}
s.onend=function(aborted){
document.getElementById('abortBtn').style.display = 'none'
document.getElementById('startBtn').style.display = ''
s = null
if (aborted) {
document.getElementById('testArea').style.display = 'none'
}
}
s.start();
}
function abortTest() {
if (s) s.abort();
}
</script>
</head>
<body>
<h1>LibreSpeed - Chart.js example</h1>
<div id="testArea" style="display:none">
<p id="ip">Please wait...</p>
<h2>Speed</h2>
<canvas id="chart1Area"></canvas>
<h2>Latency</h2>
<canvas id="chart2Area"></canvas>
<br/>
<a href="javascript:abortTest()" id="abortBtn">Abort</a>
</div>
<a href="javascript:runTest()" id="startBtn">Run speedtest</a>
<br/><br/> Charts by <a href="http://www.chartjs.org/">Chart.js</a><br/><br/><a href="https://github.com/librespeed/speedtest">Source code</a>
</body>
</html>

View File

@ -0,0 +1,174 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<title>LibreSpeed Example</title>
<link rel="shortcut icon" href="favicon.ico">
<script type="text/javascript" src="speedtest.js"></script>
<script type="text/javascript">
//INITIALIZE SPEEDTEST
var s=new Speedtest(); //create speedtest object
//CUSTOM SETTINGS HERE
s.setParameter("test_order","D_U"); //we only want download and upload test
s.setParameter("time_auto",false); //fixed duration for tests
s.setParameter("time_dl_max",10); //10 seconds for the download test
s.setParameter("time_ul_max",15); //15 seconds for the upload test
//END OF CUSTOM SETTINGS
s.onupdate=function(data){ //callback to update data in UI
I("dlText").textContent=(data.testState==1&&data.dlStatus==0)?"...":data.dlStatus;
I("ulText").textContent=(data.testState==3&&data.ulStatus==0)?"...":data.ulStatus;
}
s.onend=function(aborted){ //callback for test ended/aborted
I("startStopBtn").className=""; //show start button again
if(aborted){ //if the test was aborted, clear the UI and prepare for new test
initUI();
}
}
function startStop(){ //start/stop button pressed
if(s.getState()==3){
//speedtest is running, abort
s.abort();
}else{
//test is not running, begin
s.start();
I("startStopBtn").className="running";
}
}
//function to (re)initialize UI
function initUI(){
I("dlText").textContent="";
I("ulText").textContent="";
}
function I(id){return document.getElementById(id);}
</script>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:14em;
height:9em;
position:relative;
box-sizing:border-box;
}
div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.meterText{
position:absolute;
bottom:1.5em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
#dlText{
color:#6060AA;
}
#ulText{
color:#309030;
}
div.meterText:empty:before{
color:#505050 !important;
content:"0.00";
}
div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testGroup{
display:inline-block;
}
@media all and (max-width:65em){
body{
font-size:2vw;
}
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
div.testGroup{
display:block;
margin: 0 auto;
}
}
</style>
</head>
<body>
<h1>LibreSpeed Example</h1>
<div id="startStopBtn" onclick="startStop()"></div>
<div id="test">
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
</div>
<a href="https://github.com/librespeed/speedtest">Source code</a>
<script type="text/javascript">
initUI();
</script>
</body>
</html>

View File

@ -0,0 +1,262 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<meta charset="UTF-8" />
<link rel="shortcut icon" href="favicon.ico">
<script type="text/javascript" src="speedtest.js"></script>
<script type="text/javascript">
function I(i){return document.getElementById(i);}
//INITIALIZE SPEEDTEST
var s=new Speedtest(); //create speedtest object
var meterBk=/Trident.*rv:(\d+\.\d+)/i.test(navigator.userAgent)?"#EAEAEA":"#80808040";
var dlColor="#6060AA",
ulColor="#616161";
var progColor=meterBk;
//CODE FOR GAUGES
function drawMeter(c,amount,bk,fg,progress,prog){
var ctx=c.getContext("2d");
var dp=window.devicePixelRatio||1;
var cw=c.clientWidth*dp, ch=c.clientHeight*dp;
var sizScale=ch*0.0055;
if(c.width==cw&&c.height==ch){
ctx.clearRect(0,0,cw,ch);
}else{
c.width=cw;
c.height=ch;
}
ctx.beginPath();
ctx.strokeStyle=bk;
ctx.lineWidth=12*sizScale;
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,Math.PI*0.1);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle=fg;
ctx.lineWidth=12*sizScale;
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,amount*Math.PI*1.2-Math.PI*1.1);
ctx.stroke();
if(typeof progress !== "undefined"){
ctx.fillStyle=prog;
ctx.fillRect(c.width*0.3,c.height-16*sizScale,c.width*0.4*progress,4*sizScale);
}
}
function mbpsToAmount(s){
return 1-(1/(Math.pow(1.3,Math.sqrt(s))));
}
function format(d){
d=Number(d);
if(d<10) return d.toFixed(2);
if(d<100) return d.toFixed(1);
return d.toFixed(0);
}
//UI CODE
var uiData=null;
function startStop(){
if(s.getState()==3){
//speedtest is running, abort
s.abort();
data=null;
I("startStopBtn").className="";
initUI();
}else{
//test is not running, begin
I("startStopBtn").className="running";
s.onupdate=function(data){
uiData=data;
};
s.onend=function(aborted){
I("startStopBtn").className="";
updateUI(true);
};
s.start();
}
}
//this function reads the data sent back by the test and updates the UI
function updateUI(forced){
if(!forced&&s.getState()!=3) return;
if(uiData==null) return;
var status=uiData.testState;
I("ip").textContent=uiData.clientIp;
I("dlText").textContent=(status==1&&uiData.dlStatus==0)?"...":format(uiData.dlStatus);
drawMeter(I("dlMeter"),mbpsToAmount(Number(uiData.dlStatus*(status==1?oscillate():1))),meterBk,dlColor,Number(uiData.dlProgress),progColor);
I("ulText").textContent=(status==3&&uiData.ulStatus==0)?"...":format(uiData.ulStatus);
drawMeter(I("ulMeter"),mbpsToAmount(Number(uiData.ulStatus*(status==3?oscillate():1))),meterBk,ulColor,Number(uiData.ulProgress),progColor);
I("pingText").textContent=format(uiData.pingStatus);
I("jitText").textContent=format(uiData.jitterStatus);
}
function oscillate(){
return 1+0.02*Math.sin(Date.now()/100);
}
//update the UI every frame
window.requestAnimationFrame=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||(function(callback,element){setTimeout(callback,1000/60);});
function frame(){
requestAnimationFrame(frame);
updateUI();
}
frame(); //start frame loop
//function to (re)initialize UI
function initUI(){
drawMeter(I("dlMeter"),0,meterBk,dlColor,0);
drawMeter(I("ulMeter"),0,meterBk,ulColor,0);
I("dlText").textContent="";
I("ulText").textContent="";
I("pingText").textContent="";
I("jitText").textContent="";
I("ip").textContent="";
}
</script>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:16em;
height:12.5em;
position:relative;
box-sizing:border-box;
}
div.testArea2{
display:inline-block;
width:14em;
height:7em;
position:relative;
box-sizing:border-box;
text-align:center;
}
div.testArea div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.testArea2 div.testName{
display:block;
text-align:center;
font-size:1.4em;
}
div.testArea div.meterText{
position:absolute;
bottom:1.55em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
div.testArea2 div.meterText{
display:inline-block;
font-size:2.5em;
}
div.meterText:empty:before{
content:"0.00";
}
div.testArea div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testArea2 div.unit{
display:inline-block;
}
div.testArea canvas{
position:absolute;
top:0; left:0; width:100%; height:100%;
z-index:1;
}
div.testGroup{
display:block;
margin: 0 auto;
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
}
</style>
<title>LibreSpeed Example</title>
</head>
<body>
<h1>LibreSpeed Example</h1>
<div id="testWrapper">
<div id="startStopBtn" onclick="startStop()"></div>
<div id="test">
<div class="testGroup">
<div class="testArea2">
<div class="testName">Ping</div>
<div id="pingText" class="meterText" style="color:#AA6060"></div>
<div class="unit">ms</div>
</div>
<div class="testArea2">
<div class="testName">Jitter</div>
<div id="jitText" class="meterText" style="color:#AA6060"></div>
<div class="unit">ms</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<canvas id="dlMeter" class="meter"></canvas>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<canvas id="ulMeter" class="meter"></canvas>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div id="ipArea">
<span id="ip"></span>
</div>
</div>
<a href="https://github.com/librespeed/speedtest">Source code</a>
</div>
<script type="text/javascript">setTimeout(function(){initUI()},100);</script>
</body>
</html>

View File

@ -0,0 +1,192 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<title>LibreSpeed Example</title>
<link rel="shortcut icon" href="favicon.ico">
<script type="text/javascript" src="speedtest.js"></script>
<script type="text/javascript">
//INITIALIZE SPEEDTEST
var s=new Speedtest(); //create speedtest object
s.onupdate=function(data){ //callback to update data in UI
I("ip").textContent=data.clientIp;
I("dlText").textContent=(data.testState==1&&data.dlStatus==0)?"...":data.dlStatus;
I("ulText").textContent=(data.testState==3&&data.ulStatus==0)?"...":data.ulStatus;
I("pingText").textContent=data.pingStatus;
I("jitText").textContent=data.jitterStatus;
}
s.onend=function(aborted){ //callback for test ended/aborted
I("startStopBtn").className=""; //show start button again
if(aborted){ //if the test was aborted, clear the UI and prepare for new test
initUI();
}
}
function startStop(){ //start/stop button pressed
if(s.getState()==3){
//speedtest is running, abort
s.abort();
}else{
//test is not running, begin
s.start();
I("startStopBtn").className="running";
}
}
//function to (re)initialize UI
function initUI(){
I("dlText").textContent="";
I("ulText").textContent="";
I("pingText").textContent="";
I("jitText").textContent="";
I("ip").textContent="";
}
function I(id){return document.getElementById(id);}
</script>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:14em;
height:9em;
position:relative;
box-sizing:border-box;
}
div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.meterText{
position:absolute;
bottom:1.5em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
#dlText{
color:#6060AA;
}
#ulText{
color:#309030;
}
#pingText,#jitText{
color:#AA6060;
}
div.meterText:empty:before{
color:#505050 !important;
content:"0.00";
}
div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testGroup{
display:inline-block;
}
@media all and (max-width:65em){
body{
font-size:1.5vw;
}
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
div.testGroup{
display:block;
margin: 0 auto;
}
}
</style>
</head>
<body>
<h1>LibreSpeed Example</h1>
<div id="startStopBtn" onclick="startStop()"></div>
<div id="test">
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Ping</div>
<div id="pingText" class="meterText"></div>
<div class="unit">ms</div>
</div>
<div class="testArea">
<div class="testName">Jitter</div>
<div id="jitText" class="meterText"></div>
<div class="unit">ms</div>
</div>
</div>
<div id="ipArea">
IP Address: <span id="ip"></span>
</div>
</div>
<a href="https://github.com/librespeed/speedtest">Source code</a>
<script type="text/javascript">
initUI();
</script>
</body>
</html>

View File

@ -0,0 +1,212 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<title>LibreSpeed Example</title>
<link rel="shortcut icon" href="favicon.ico">
<script type="text/javascript" src="speedtest.js"></script>
<script type="text/javascript">
//INITIALIZE SPEEDTEST
var s=new Speedtest(); //create speedtest object
s.onupdate=function(data){ //callback to update data in UI
I("ip").textContent=data.clientIp;
I("dlText").textContent=(data.testState==1&&data.dlStatus==0)?"...":data.dlStatus;
I("ulText").textContent=(data.testState==3&&data.ulStatus==0)?"...":data.ulStatus;
I("pingText").textContent=data.pingStatus;
I("jitText").textContent=data.jitterStatus;
var prog=(Number(data.dlProgress)*2+Number(data.ulProgress)*2+Number(data.pingProgress))/5;
I("progress").style.width=(100*prog)+"%";
}
s.onend=function(aborted){ //callback for test ended/aborted
I("startStopBtn").className=""; //show start button again
if(aborted){ //if the test was aborted, clear the UI and prepare for new test
initUI();
}
}
function startStop(){ //start/stop button pressed
if(s.getState()==3){
//speedtest is running, abort
s.abort();
}else{
//test is not running, begin
s.start();
I("startStopBtn").className="running";
}
}
//function to (re)initialize UI
function initUI(){
I("dlText").textContent="";
I("ulText").textContent="";
I("pingText").textContent="";
I("jitText").textContent="";
I("ip").textContent="";
}
function I(id){return document.getElementById(id);}
</script>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:14em;
height:9em;
position:relative;
box-sizing:border-box;
}
div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.meterText{
position:absolute;
bottom:1.5em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
#dlText{
color:#6060AA;
}
#ulText{
color:#309030;
}
#pingText,#jitText{
color:#AA6060;
}
div.meterText:empty:before{
color:#505050 !important;
content:"0.00";
}
div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testGroup{
display:inline-block;
}
@media all and (max-width:65em){
body{
font-size:1.5vw;
}
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
div.testGroup{
display:block;
margin: 0 auto;
}
}
#progressBar{
width:90%;
height:0.3em;
background-color:#EEEEEE;
position:relative;
display:block;
margin:0 auto;
margin-bottom:2em;
}
#progress{
position:absolute;
top:0; left:0;
height:100%;
width:0%;
transition: width 2s;
background-color:#90BBFF;
}
</style>
</head>
<body>
<h1>LibreSpeed Example</h1>
<div id="startStopBtn" onclick="startStop()"></div>
<div id="test">
<div id="progressBar"><div id="progress"></div></div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Ping</div>
<div id="pingText" class="meterText"></div>
<div class="unit">ms</div>
</div>
<div class="testArea">
<div class="testName">Jitter</div>
<div id="jitText" class="meterText"></div>
<div class="unit">ms</div>
</div>
</div>
<div id="ipArea">
IP Address: <span id="ip"></span>
</div>
</div>
<a href="https://github.com/librespeed/speedtest">Source code</a>
<script type="text/javascript">
initUI();
</script>
</body>
</html>

820
web/assets/index.html Normal file → Executable file
View File

@ -1,479 +1,323 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no"/>
<meta charset="UTF-8"/>
<link rel="shortcut icon" href="../../favicon.ico">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Oswald:300,400|Source+Sans+Pro:400,600">
<script type="text/javascript" src="speedtest.js"></script>
<script type="text/javascript">
function I(i) {
return document.getElementById(i);
}
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<meta charset="UTF-8" />
<link rel="shortcut icon" href="favicon.ico">
<script type="text/javascript" src="speedtest.js"></script>
<script type="text/javascript">
function I(i){return document.getElementById(i);}
//INITIALIZE SPEEDTEST
var s=new Speedtest(); //create speedtest object
s.setParameter("telemetry_level","basic"); //enable telemetry
var meterBk = "#ebecf0"; // ui--input-border
var dlColor = "#02b159", // grass-green
ulColor = "#02b159", // grass-green
pingColor = "#02b159", // grass-green
jitColor = "#02b159"; // grass-green
var progColor = "#0ac9f7"; // sky-blue
var meterBk=/Trident.*rv:(\d+\.\d+)/i.test(navigator.userAgent)?"#EAEAEA":"#80808040";
var dlColor="#6060AA",
ulColor="#616161";
var progColor=meterBk;
function setPageTitle() {
var pageTitleDiv = document.getElementById('pageTitle');
var titlePrefixNode = document.createTextNode("You're currently using this server:");
var lineBreakElement = document.createElement('br');
var locationNode = document.createTextNode('Unknown');
switch (window.location.hostname) {
case 'us1.zzz.cat':
locationNode.textContent = 'Unites States 1 (Fremont, Linode)';
break;
case 'us2.zzz.cat':
locationNode.textContent = 'Unites States 2 (LAX (CN2), Bandwagon)';
break;
case 'us3.zzz.cat':
locationNode.textContent = 'Unites States 3 (LAX (CN2 GIA-E), Bandwagon)';
break;
case 'jp1.zzz.cat':
locationNode.textContent = 'Japan 1 (Tokyo 2, Linode)';
break;
case 'jp2.zzz.cat':
locationNode.textContent = 'Japan 2 (Tokyo 2, Linode)';
break;
case 'sg1.zzz.cat':
locationNode.textContent = 'Singapore (Singapore, Linode)';
break;
case 'ca1.zzz.cat':
locationNode.textContent = 'Canada (Toronto, Linode)';
break;
default:
locationNode.textContent = window.location.hostname;
break;
}
//CODE FOR GAUGES
function drawMeter(c,amount,bk,fg,progress,prog){
var ctx=c.getContext("2d");
var dp=window.devicePixelRatio||1;
var cw=c.clientWidth*dp, ch=c.clientHeight*dp;
var sizScale=ch*0.0055;
if(c.width==cw&&c.height==ch){
ctx.clearRect(0,0,cw,ch);
}else{
c.width=cw;
c.height=ch;
}
ctx.beginPath();
ctx.strokeStyle=bk;
ctx.lineWidth=12*sizScale;
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,Math.PI*0.1);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle=fg;
ctx.lineWidth=12*sizScale;
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,amount*Math.PI*1.2-Math.PI*1.1);
ctx.stroke();
if(typeof progress !== "undefined"){
ctx.fillStyle=prog;
ctx.fillRect(c.width*0.3,c.height-16*sizScale,c.width*0.4*progress,4*sizScale);
}
}
function mbpsToAmount(s){
return 1-(1/(Math.pow(1.3,Math.sqrt(s))));
}
function format(d){
d=Number(d);
if(d<10) return d.toFixed(2);
if(d<100) return d.toFixed(1);
return d.toFixed(0);
}
pageTitleDiv.appendChild(titlePrefixNode);
pageTitleDiv.appendChild(lineBreakElement);
pageTitleDiv.appendChild(locationNode);
}
//INITIALIZE SPEEDTEST
var s = new Speedtest(); //create speedtest object
//CODE FOR GAUGES
function drawMeter(c, amount, bk, fg, progress, prog) {
var ctx = c.getContext("2d");
var dp = window.devicePixelRatio || 1;
var cw = c.clientWidth * dp, ch = c.clientHeight * dp;
var sizScale = ch * 0.0055;
if (c.width == cw && c.height == ch) {
ctx.clearRect(0, 0, cw, ch);
} else {
c.width = cw;
c.height = ch;
}
ctx.beginPath();
ctx.strokeStyle = bk;
ctx.lineWidth = 4 * sizScale;
ctx.arc(c.width / 2, c.height / 2, c.height / 2 - ctx.lineWidth, -Math.PI * 1.2, Math.PI * 0.2);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = fg;
ctx.lineWidth = 4 * sizScale;
ctx.arc(c.width / 2, c.height / 2, c.height / 2 - ctx.lineWidth, -Math.PI * 1.2, amount * Math.PI * 1.2 - Math.PI * 1.2);
ctx.stroke();
if (typeof progress !== "undefined") {
ctx.fillStyle = prog;
ctx.fillRect(c.width * 0.3, c.height - 28 * sizScale, c.width * 0.4 * progress, 4 * sizScale);
}
}
function mbpsToAmount(s) {
return 1 - (1 / (Math.pow(1.3, Math.sqrt(s))));
}
function msToAmount(s) {
return 1 - (1 / (Math.pow(1.08, Math.sqrt(s))));
}
function format(d) {
d = Number(d);
if (d < 10) return d.toFixed(2);
if (d < 100) return d.toFixed(1);
return d.toFixed(0);
}
//UI CODE
var uiData = null;
function startStop() {
if (s.getState() == 3) {
//speedtest is running, abort
s.abort();
data = null;
I("startStopBtn").className = "";
initUI();
} else {
//test is not running, begin
I("startStopBtn").className = "running";
I("shareArea").style.display = "none";
s.onupdate = function (data) {
uiData = data;
};
s.onend = function (aborted) {
I("startStopBtn").className = "";
updateUI(true);
if (!aborted) {
//if testId is present, show sharing panel, otherwise do nothing
try {
var testId = uiData.testId;
if (testId != null) {
var shareURL = window.location.href.substring(0, window.location.href.lastIndexOf("/")) + "/results?id=" + testId;
I("resultsImg").src = shareURL;
I("resultsURL").value = shareURL;
I("testId").innerHTML = testId;
I("shareArea").style.display = "";
}
} catch (e) {
}
//UI CODE
var uiData=null;
function startStop(){
if(s.getState()==3){
//speedtest is running, abort
s.abort();
data=null;
I("startStopBtn").className="";
initUI();
}else{
//test is not running, begin
I("startStopBtn").className="running";
I("shareArea").style.display="none";
s.onupdate=function(data){
uiData=data;
};
s.onend=function(aborted){
I("startStopBtn").className="";
updateUI(true);
if(!aborted){
//if testId is present, show sharing panel, otherwise do nothing
try{
var testId=uiData.testId;
if(testId!=null){
var shareURL=window.location.href.substring(0,window.location.href.lastIndexOf("/"))+"/results/?id="+testId;
I("resultsImg").src=shareURL;
I("resultsURL").value=shareURL;
I("testId").innerHTML=testId;
I("shareArea").style.display="";
}
};
s.start();
}catch(e){}
}
}
//this function reads the data sent back by the test and updates the UI
function updateUI(forced) {
if (!forced && s.getState() != 3) return;
if (uiData == null) return;
var status = uiData.testState;
I("ip").textContent = "IP Address: " + uiData.clientIp;
I("dlText").textContent = (status == 1 && uiData.dlStatus == 0) ? "..." : format(uiData.dlStatus);
drawMeter(I("dlMeter"), mbpsToAmount(Number(uiData.dlStatus * (status == 1 ? oscillate() : 1))), meterBk, dlColor, Number(uiData.dlProgress), progColor);
I("ulText").textContent = (status == 3 && uiData.ulStatus == 0) ? "..." : format(uiData.ulStatus);
drawMeter(I("ulMeter"), mbpsToAmount(Number(uiData.ulStatus * (status == 3 ? oscillate() : 1))), meterBk, ulColor, Number(uiData.ulProgress), progColor);
I("pingText").textContent = format(uiData.pingStatus);
drawMeter(I("pingMeter"), msToAmount(Number(uiData.pingStatus * (status == 2 ? oscillate() : 1))), meterBk, pingColor, Number(uiData.pingProgress), progColor);
I("jitText").textContent = format(uiData.jitterStatus);
drawMeter(I("jitMeter"), msToAmount(Number(uiData.jitterStatus * (status == 2 ? oscillate() : 1))), meterBk, jitColor, Number(uiData.pingProgress), progColor);
}
function oscillate() {
return 1 + 0.02 * Math.sin(Date.now() / 100);
}
//update the UI every frame
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || (function (callback, element) {
setTimeout(callback, 1000 / 60);
});
function frame() {
requestAnimationFrame(frame);
updateUI();
}
frame(); //start frame loop
//function to (re)initialize UI
function initUI() {
drawMeter(I("dlMeter"), 0, meterBk, dlColor, 0);
drawMeter(I("ulMeter"), 0, meterBk, ulColor, 0);
drawMeter(I("pingMeter"), 0, meterBk, pingColor, 0);
drawMeter(I("jitMeter"), 0, meterBk, jitColor, 0);
I("dlText").textContent = "";
I("ulText").textContent = "";
I("pingText").textContent = "";
I("jitText").textContent = "";
I("ip").textContent = "";
}
</script>
<style type="text/css">
:root {
--dark-gray: #32363b;
--grass-green: #02b159;
}
html, body {
background-color: white;
border: none;
box-sizing: border-box;
color: var(--dark-gray);
padding: 0;
margin: 0;
}
html {
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
font-size: 18px;
font-size: 1.125rem;
line-height: 1.33333;
font-family: "Source Sans Pro", sans-serif;
font-weight: 400;
letter-spacing: 0;
margin: 1rem 1rem 2rem;
text-align: center;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
}
*, *:before, *:after {
box-sizing: inherit;
}
#startStopBtn {
display: inline-block;
margin: 0 auto;
padding-left: 2rem;
padding-right: 2rem;
color: white;
background-color: var(--grass-green);
border: 0.15em solid var(--grass-green);
border-radius: 0.3em;
transition: all 0.3s;
box-sizing: border-box;
height: 3em;
line-height: 2.7em;
cursor: pointer;
/*box-shadow:0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);*/
font-weight: 600;
}
#startStopBtn:hover {
/*box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);*/
}
#startStopBtn.running {
background-color: var(--dark-gray);
border-color: var(--dark-gray);
color: white;
}
#startStopBtn:before {
content: "Start Speed Test";
}
#startStopBtn.running:before {
content: "Abort";
}
#test {
margin-top: 2rem;
margin-bottom: 2rem;
}
div.testArea {
display: inline-block;
width: calc(100% - 2rem - 2rem); /* subtract margins */
max-width: 16em;
margin: 2rem;
position: relative;
box-sizing: border-box;
}
div.testArea::before {
content: '';
display: block;
padding-top: 100%;
}
div.testName {
bottom: 0;
font-size: 1rem;
font-weight: 600;
left: 0;
position: absolute;
width: 100%;
z-index: 9;
}
div.meterText {
bottom: 1.8em;
font-family: "Oswald", sans-serif;
font-size: 4em;
font-weight: 300;
left: 0;
line-height: 1;
position: absolute;
width: 100%;
z-index: 9;
}
div.meterText:empty:before {
content: "0.00";
}
div.unit {
bottom: 3.5em;
font-family: "Oswald", sans-serif;
font-size: 1.5em;
font-weight: 400;
letter-spacing: 0.5px;
left: 0;
line-height: 1;
position: absolute;
text-transform: uppercase;
width: 100%;
z-index: 9;
}
div.testArea canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
#ipArea {
margin-top: 2rem;
}
#shareArea {
width: 95%;
max-width: 25em;
margin: 0 auto;
margin-top: 2em;
}
#shareArea > * {
display: block;
width: 100%;
height: auto;
margin: 0.25em 0;
}
#privacyPolicy {
position: fixed;
top: 10em;
bottom: 10em;
left: 15em;
right: 15em;
overflow-y: auto;
width: auto;
height: auto;
box-shadow: 0 0 3em 1em #000000;
z-index: 999999;
text-align: left;
background-color: #FFFFFF;
padding: 1em;
}
#privacyCloseBtn {
display: inline-block;
margin: 0 auto;
padding-left: 2rem;
padding-right: 2rem;
color: white;
background-color: var(--grass-green);
border: 0.15em solid var(--grass-green);
border-radius: 0.3em;
transition: all 0.3s;
box-sizing: border-box;
height: 3em;
line-height: 2.7em;
cursor: pointer;
/*box-shadow:0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);*/
font-weight: 600;
}
#resultsURL {
color: #333;
font-size: 1.2rem;
padding: 0.5rem 1rem;
border-radius: 0.2rem;
background-color: rgb(255, 255, 255);
border: green;
display: block;
border-style: dashed;
border-width: 2px;
margin: 0.8em 0 0.8em 0;
}
#testId {
margin-top: 1.4em;
}
a.privacy {
margin-top: 2em;
text-align: center;
font-size: 0.8em;
color: #808080;
display: block;
}
@media all and (max-width: 65em) {
/* 1040 px */
div.testGroup {
margin-left: -1rem;
margin-right: -1rem;
}
div.testArea {
font-size: 1.5384615vw; /* 16 px */
margin: 1rem;
width: calc(100% - 1rem - 1rem); /* subtract margins */
}
}
@media all and (max-width: 40em) {
/* 640 px */
div.testArea {
font-size: 0.8em; /* 12.8 px */
}
}
</style>
<title>zzz.cat Speedtest</title>
};
s.start();
}
}
//this function reads the data sent back by the test and updates the UI
function updateUI(forced){
if(!forced&&s.getState()!=3) return;
if(uiData==null) return;
var status=uiData.testState;
I("ip").textContent=uiData.clientIp;
I("dlText").textContent=(status==1&&uiData.dlStatus==0)?"...":format(uiData.dlStatus);
drawMeter(I("dlMeter"),mbpsToAmount(Number(uiData.dlStatus*(status==1?oscillate():1))),meterBk,dlColor,Number(uiData.dlProgress),progColor);
I("ulText").textContent=(status==3&&uiData.ulStatus==0)?"...":format(uiData.ulStatus);
drawMeter(I("ulMeter"),mbpsToAmount(Number(uiData.ulStatus*(status==3?oscillate():1))),meterBk,ulColor,Number(uiData.ulProgress),progColor);
I("pingText").textContent=format(uiData.pingStatus);
I("jitText").textContent=format(uiData.jitterStatus);
}
function oscillate(){
return 1+0.02*Math.sin(Date.now()/100);
}
//update the UI every frame
window.requestAnimationFrame=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||(function(callback,element){setTimeout(callback,1000/60);});
function frame(){
requestAnimationFrame(frame);
updateUI();
}
frame(); //start frame loop
//function to (re)initialize UI
function initUI(){
drawMeter(I("dlMeter"),0,meterBk,dlColor,0);
drawMeter(I("ulMeter"),0,meterBk,ulColor,0);
I("dlText").textContent="";
I("ulText").textContent="";
I("pingText").textContent="";
I("jitText").textContent="";
I("ip").textContent="";
}
</script>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:16em;
height:12.5em;
position:relative;
box-sizing:border-box;
}
div.testArea2{
display:inline-block;
width:14em;
height:7em;
position:relative;
box-sizing:border-box;
text-align:center;
}
div.testArea div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.testArea2 div.testName{
display:block;
text-align:center;
font-size:1.4em;
}
div.testArea div.meterText{
position:absolute;
bottom:1.55em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
div.testArea2 div.meterText{
display:inline-block;
font-size:2.5em;
}
div.meterText:empty:before{
content:"0.00";
}
div.testArea div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testArea2 div.unit{
display:inline-block;
}
div.testArea canvas{
position:absolute;
top:0; left:0; width:100%; height:100%;
z-index:1;
}
div.testGroup{
display:block;
margin: 0 auto;
}
#shareArea{
width:95%;
max-width:40em;
margin:0 auto;
margin-top:2em;
}
#shareArea > *{
display:block;
width:100%;
height:auto;
margin: 0.25em 0;
}
#privacyPolicy{
position:fixed;
top:2em;
bottom:2em;
left:2em;
right:2em;
overflow-y:auto;
width:auto;
height:auto;
box-shadow:0 0 3em 1em #000000;
z-index:999999;
text-align:left;
background-color:#FFFFFF;
padding:1em;
}
a.privacy{
text-align:center;
font-size:0.8em;
color:#808080;
padding: 0 3em;
}
div.closePrivacyPolicy {
width: 100%;
text-align: center;
}
div.closePrivacyPolicy a.privacy {
padding: 1em 3em;
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
}
</style>
<title>LibreSpeed Example</title>
</head>
<body>
<h1>LibreSpeed Example</h1>
<div id="testWrapper">
<h1 id="pageTitle"></h1>
<div id="startStopBtn" onclick="startStop()"></div>
<br/>
<div id="test">
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<canvas id="dlMeter" class="meter"></canvas>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbit/s</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<canvas id="ulMeter" class="meter"></canvas>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbit/s</div>
</div>
<div class="testArea">
<div class="testName">Ping</div>
<canvas id="pingMeter" class="meter"></canvas>
<div id="pingText" class="meterText"></div>
<div class="unit">ms</div>
</div>
<div class="testArea">
<div class="testName">Jitter</div>
<canvas id="jitMeter" class="meter"></canvas>
<div id="jitText" class="meterText"></div>
<div class="unit">ms</div>
</div>
</div>
<div id="ipArea">
<span id="ip"></span>
</div>
<div><a class="privacy" href="#" onclick="I('privacyPolicy').style.display=''">Privacy</a></div>
<div id="shareArea" style="display:none">
<h3>Share your results</h3>
<input type="text" value="" id="resultsURL" readonly="readonly"
onclick="this.select();this.focus();this.select();document.execCommand('copy');alert('Link copied')"/>
<img src="" id="resultsImg"/>
<p>Test ID: <span id="testId"></span></p>
</div>
</div>
<div id="startStopBtn" onclick="startStop()"></div><br/>
<a class="privacy" href="#" onclick="I('privacyPolicy').style.display=''">Privacy</a>
<div id="test">
<div class="testGroup">
<div class="testArea2">
<div class="testName">Ping</div>
<div id="pingText" class="meterText" style="color:#AA6060"></div>
<div class="unit">ms</div>
</div>
<div class="testArea2">
<div class="testName">Jitter</div>
<div id="jitText" class="meterText" style="color:#AA6060"></div>
<div class="unit">ms</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<canvas id="dlMeter" class="meter"></canvas>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<canvas id="ulMeter" class="meter"></canvas>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div id="ipArea">
<span id="ip"></span>
</div>
<div id="shareArea" style="display:none">
<h3>Share results</h3>
<p>Test ID: <span id="testId"></span></p>
<input type="text" value="" id="resultsURL" readonly="readonly" onclick="this.select();this.focus();this.select();document.execCommand('copy');alert('Link copied')"/>
<img src="" id="resultsImg" />
</div>
</div>
<a href="https://github.com/librespeed/speedtest">Source code</a>
</div>
<div id="privacyPolicy" style="display:none">
<h2>Privacy Policy</h2>
@ -481,25 +325,25 @@
<h4>What data we collect</h4>
<p>
At the end of the test, the following data is collected and stored:
<ul>
<li>Test ID</li>
<li>Time of testing</li>
<li>Test results (download and upload speed, ping and jitter)</li>
<li>IP address</li>
<li>ISP information</li>
<li>Approximate location (inferred from IP address, not GPS)</li>
<li>User agent and browser locale</li>
<li>Test log (contains no personal information)</li>
</ul>
<ul>
<li>Test ID</li>
<li>Time of testing</li>
<li>Test results (download and upload speed, ping and jitter)</li>
<li>IP address</li>
<li>ISP information</li>
<li>Approximate location (inferred from IP address, not GPS)</li>
<li>User agent and browser locale</li>
<li>Test log (contains no personal information)</li>
</ul>
</p>
<h4>How we use the data</h4>
<p>
Data collected through this service is used to:
<ul>
<li>Allow sharing of test results (sharable image for forums, etc.)</li>
<li>To improve the service offered to you (for instance, to detect problems on our side)</li>
</ul>
No personal information is disclosed to third parties.
<ul>
<li>Allow sharing of test results (sharable image for forums, etc.)</li>
<li>To improve the service offered to you (for instance, to detect problems on our side)</li>
</ul>
No personal information is disclosed to third parties.
</p>
<h4>Your consent</h4>
<p>
@ -507,17 +351,15 @@
</p>
<h4>Data removal</h4>
<p>
If you want to have your information deleted, you need to provide either the ID of the test or your IP address.
This is the only way to identify your data, without this information we won't be able to comply with your
request.<br/><br/>
Contact this email address for all deletion requests: maddie at zzz dot cat.
If you want to have your information deleted, you need to provide either the ID of the test or your IP address. This is the only way to identify your data, without this information we won't be able to comply with your request.<br/><br/>
Contact this email address for all deletion requests: <a href="mailto:PUT@YOUR_EMAIL.HERE">TO BE FILLED BY DEVELOPER</a>.
</p>
<br/><br/>
<a id="privacyCloseBtn" class="privacy" href="#" onclick="I('privacyPolicy').style.display='none'">Close</a><br/>
<div class="closePrivacyPolicy">
<a class="privacy" href="#" onclick="I('privacyPolicy').style.display='none'">Close</a>
</div>
<br/>
</div>
<script type="text/javascript">setPageTitle();</script>
<script type="text/javascript">setTimeout(function () {
initUI()
}, 100);</script>
<script type="text/javascript">setTimeout(function(){initUI()},100);</script>
</body>
</html>

View File

@ -1,508 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no"/>
<title>zzz.cat Speedtest</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Oswald:300,400|Source+Sans+Pro:400,600"
type="text/css" media="all"/>
<style type="text/css">
:root {
--dark-gray: #32363b;
--grass-green: #02b159;
}
html, body {
background-color: white;
border: none;
box-sizing: border-box;
color: var(--dark-gray);
padding: 0;
margin: 0;
}
html {
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
font-size: 18px;
font-size: 1.125rem;
line-height: 1.33333;
font-family: "Source Sans Pro", sans-serif;
font-weight: 400;
letter-spacing: 0;
margin: 1rem 1rem 2rem;
text-align: center;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
}
*, *:before, *:after {
box-sizing: inherit;
}
#startStopBtn {
display: inline-block;
margin: 0 auto;
padding-left: 2rem;
padding-right: 2rem;
color: white;
background-color: var(--grass-green);
border: 0.15em solid var(--grass-green);
border-radius: 0.3em;
transition: all 0.3s;
box-sizing: border-box;
height: 3em;
line-height: 2.7em;
cursor: pointer;
/*box-shadow:0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);*/
font-weight: 600;
}
#startStopBtn:hover {
/*box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);*/
}
#startStopBtn.running {
background-color: var(--dark-gray);
border-color: var(--dark-gray);
color: white;
}
#startStopBtn:before {
content: "Start Speed Test";
}
#startStopBtn.running:before {
content: "Abort";
}
#test {
margin-top: 2rem;
margin-bottom: 2rem;
}
div.testArea {
display: inline-block;
width: calc(100% - 2rem - 2rem); /* subtract margins */
max-width: 16em;
margin: 2rem;
position: relative;
box-sizing: border-box;
}
div.testArea::before {
content: '';
display: block;
padding-top: 100%;
}
div.testName {
bottom: 0;
font-size: 1rem;
font-weight: 600;
left: 0;
position: absolute;
width: 100%;
z-index: 9;
}
div.meterText {
bottom: 1.8em;
font-family: "Oswald", sans-serif;
font-size: 4em;
font-weight: 300;
left: 0;
line-height: 1;
position: absolute;
width: 100%;
z-index: 9;
}
div.meterText:empty:before {
content: "0.00";
}
div.unit {
bottom: 3.5em;
font-family: "Oswald", sans-serif;
font-size: 1.5em;
font-weight: 400;
letter-spacing: 0.5px;
left: 0;
line-height: 1;
position: absolute;
text-transform: uppercase;
width: 100%;
z-index: 9;
}
div.testArea canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
#ipArea {
margin-top: 2rem;
}
#shareArea {
width: 95%;
max-width: 40em;
margin: 0 auto;
margin-top: 2em;
}
#shareArea > * {
display: block;
width: 100%;
height: auto;
margin: 0.25em 0;
}
#privacyPolicy {
position: fixed;
top: 10em;
bottom: 10em;
left: 15em;
right: 15em;
overflow-y: auto;
width: auto;
height: auto;
box-shadow: 0 0 3em 1em #000000;
z-index: 999999;
text-align: left;
background-color: #FFFFFF;
padding: 1em;
}
#privacyCloseBtn {
display: inline-block;
margin: 0 auto;
padding-left: 2rem;
padding-right: 2rem;
color: white;
background-color: var(--grass-green);
border: 0.15em solid var(--grass-green);
border-radius: 0.3em;
transition: all 0.3s;
box-sizing: border-box;
height: 3em;
line-height: 2.7em;
cursor: pointer;
/*box-shadow:0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);*/
font-weight: 600;
}
a.privacy {
margin-top: 2em;
text-align: center;
font-size: 0.8em;
color: #808080;
display: block;
}
@media all and (max-width: 65em) {
/* 1040 px */
div.testGroup {
margin-left: -1rem;
margin-right: -1rem;
}
div.testArea {
font-size: 1.5384615vw; /* 16 px */
margin: 1rem;
width: calc(100% - 1rem - 1rem); /* subtract margins */
}
}
@media all and (max-width: 40em) {
/* 640 px */
div.testArea {
font-size: 0.8em; /* 12.8 px */
}
}
</style>
<script type="text/javascript">
function I(id) {
return document.getElementById(id);
}
var meterBk = "#ebecf0"; // ui--input-border
var dlColor = "#02b159", // grass-green
ulColor = "#02b159", // grass-green
pingColor = "#02b159", // grass-green
jitColor = "#02b159"; // grass-green
var progColor = "#0ac9f7"; // sky-blue
function setPageTitle() {
var pageTitleDiv = document.getElementById('pageTitle');
var titlePrefixNode = document.createTextNode('Speed Test:');
var lineBreakElement = document.createElement('br');
var locationNode = document.createTextNode('Unknown');
switch (window.location.hostname) {
case 'us1.zzz.cat':
locationNode.textContent = 'Unites States 1 (Fremont, Linode)';
break;
case 'us2.zzz.cat':
locationNode.textContent = 'Unites States 2 (LAX (CN2), Bandwagon)';
break;
case 'us3.zzz.cat':
locationNode.textContent = 'Unites States 3 (LAX (CN2 GIA-E), Bandwagon)';
break;
case 'jp1.zzz.cat':
locationNode.textContent = 'Japan 1 (Tokyo 2, Linode)';
break;
case 'jp2.zzz.cat':
locationNode.textContent = 'Japan 2 (Tokyo 2, Linode)';
break;
case 'sg1.zzz.cat':
locationNode.textContent = 'Singapore (Singapore, Linode)';
break;
case 'ca1.zzz.cat':
locationNode.textContent = 'Canada (Toronto, Linode)';
break;
default:
locationNode.textContent = window.location.hostname;
break;
}
pageTitleDiv.appendChild(titlePrefixNode);
pageTitleDiv.appendChild(lineBreakElement);
pageTitleDiv.appendChild(locationNode);
}
//CODE FOR GAUGES
function drawMeter(c, amount, bk, fg, progress, prog) {
var ctx = c.getContext("2d");
var dp = window.devicePixelRatio || 1;
var cw = c.clientWidth * dp, ch = c.clientHeight * dp;
var sizScale = ch * 0.0055;
if (c.width == cw && c.height == ch) {
ctx.clearRect(0, 0, cw, ch);
} else {
c.width = cw;
c.height = ch;
}
ctx.beginPath();
ctx.strokeStyle = bk;
ctx.lineCap = "round";
ctx.lineWidth = 4 * sizScale;
ctx.arc(
c.width / 2,
c.height / 2,// c.height/2-58*sizScale,
c.height / 2 - ctx.lineWidth,//c.height/1.8-ctx.lineWidth,
-Math.PI * 1.2,
Math.PI * 0.2
);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = fg;
ctx.lineCap = (amount > 0 ? "round" : "butt");
ctx.lineWidth = 4 * sizScale;
ctx.arc(
c.width / 2,
c.height / 2,// c.height/2-58*sizScale,
c.height / 2 - ctx.lineWidth,//c.height/1.8-ctx.lineWidth,
-Math.PI * 1.2,
amount * Math.PI * 1.2 - Math.PI * 1.2
);
ctx.stroke();
if (typeof progress !== "undefined") {
ctx.fillStyle = prog;
ctx.fillRect(
c.width * 0.3,
c.height - 28 * sizScale,
c.width * 0.4 * progress,
4 * sizScale
);
}
}
function mbpsToAmount(s) {
return 1 - (1 / (Math.pow(1.3, Math.sqrt(s))));
}
function msToAmount(s) {
return 1 - (1 / (Math.pow(1.08, Math.sqrt(s))));
}
//SPEEDTEST AND UI CODE
var w = null; //speedtest worker
var data = null; //data from worker
function startStop() {
if (w != null) {
//speedtest is running, abort
w.postMessage('abort');
w = null;
data = null;
I("startStopBtn").className = "";
initUI();
} else {
//test is not running, begin
w = new Worker('speedtest_worker.js');
w.postMessage('start'); //Add optional parameters as a JSON object to this command
I("startStopBtn").className = "running";
w.onmessage = function (e) {
data = JSON.parse(e.data);
var status = data.testState;
if (status >= 4) {
//test completed
I("startStopBtn").className = "";
w = null;
updateUI(true);
}
};
}
}
//this function reads the data sent back by the worker and updates the UI
function updateUI(forced) {
if (!forced && (!data || !w)) return;
console.log(data);
var status = data.testState;
I("ip").textContent = data.clientIp;
I("dlText").textContent = (status == 1 && data.dlStatus == 0) ? "..." : data.dlStatus;
drawMeter(I("dlMeter"), mbpsToAmount(Number(data.dlStatus * (status == 1 ? oscillate() : 1))), meterBk, dlColor, Number(data.dlProgress), progColor);
I("ulText").textContent = (status == 3 && data.ulStatus == 0) ? "..." : data.ulStatus;
drawMeter(I("ulMeter"), mbpsToAmount(Number(data.ulStatus * (status == 3 ? oscillate() : 1))), meterBk, ulColor, Number(data.ulProgress), progColor);
I("pingText").textContent = data.pingStatus;
drawMeter(I("pingMeter"), msToAmount(Number(data.pingStatus * (status == 2 ? oscillate() : 1))), meterBk, pingColor, Number(data.pingProgress), progColor);
I("jitText").textContent = data.jitterStatus;
drawMeter(I("jitMeter"), msToAmount(Number(data.jitterStatus * (status == 2 ? oscillate() : 1))), meterBk, jitColor, Number(data.pingProgress), progColor);
}
function oscillate() {
return 1 + 0.02 * Math.sin(Date.now() / 100);
}
//poll the status from the worker (this will call updateUI)
setInterval(function () {
if (w) w.postMessage('status');
}, 200);
//update the UI every frame
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || (function (callback, element) {
setTimeout(callback, 1000 / 60);
});
function frame() {
requestAnimationFrame(frame);
updateUI();
}
frame(); //start frame loop
//function to (re)initialize UI
function initUI() {
drawMeter(I("dlMeter"), 0, meterBk, dlColor, 0);
drawMeter(I("ulMeter"), 0, meterBk, ulColor, 0);
drawMeter(I("pingMeter"), 0, meterBk, pingColor, 0);
drawMeter(I("jitMeter"), 0, meterBk, jitColor, 0);
I("dlText").textContent = "";
I("ulText").textContent = "";
I("pingText").textContent = "";
I("jitText").textContent = "";
I("ip").textContent = "";
}
</script>
</head>
<body>
<div>
<h1 id="pageTitle"></h1>
<div id="test">
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<canvas id="dlMeter" class="meter"></canvas>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbit/s</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<canvas id="ulMeter" class="meter"></canvas>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbit/s</div>
</div>
<div class="testArea">
<div class="testName">Ping</div>
<canvas id="pingMeter" class="meter"></canvas>
<div id="pingText" class="meterText"></div>
<div class="unit">ms</div>
</div>
<div class="testArea">
<div class="testName">Jitter</div>
<canvas id="jitMeter" class="meter"></canvas>
<div id="jitText" class="meterText"></div>
<div class="unit">ms</div>
</div>
</div>
<div id="ipArea">
IP Address: <span id="ip"></span>
</div>
<div id="shareArea" style="display:none">
<h3>Share results</h3>
<p>Test ID: <span id="testId"></span></p>
<input type="text" value="" id="resultsURL" readonly="readonly"
onclick="this.select();this.focus();this.select();document.execCommand('copy');alert('Link copied')"/>
<img src="" id="resultsImg"/>
</div>
</div>
<div id="startStopBtn" onclick="startStop()"></div>
<div id="privacyPolicy" style="display:none;">
<h2>Privacy Policy</h2>
<p>This HTML5 Speedtest server is configured with telemetry enabled.</p>
<h4>What data we collect</h4>
<p>
At the end of the test, the following data is collected and stored:
<ul>
<li>Test ID</li>
<li>Time of testing</li>
<li>Test results (download and upload speed, ping and jitter)</li>
<li>IP address</li>
<li>ISP information</li>
<li>Approximate location (inferred from IP address, not GPS)</li>
<li>User agent and browser locale</li>
<li>Test log (contains no personal information)</li>
</ul>
</p>
<h4>How we use the data</h4>
<p>
Data collected through this service is used to:
<ul>
<li>Allow sharing of test results (sharable image for forums, etc.)</li>
<li>To improve the service offered to you (for instance, to detect problems on our side)</li>
</ul>
No personal information is disclosed to third parties.
</p>
<h4>Your consent</h4>
<p>
By starting the test, you consent to the terms of this privacy policy.
</p>
<h4>Data removal</h4>
<p>
If you want to have your information deleted, you need to provide either the ID of the test or your IP
address. This is the only way to identify your data, without this information we won't be able to comply
with your request.<br/><br/>
Contact this email address for all deletion requests: maddie at zzz dot cat</a>.
</p>
<br/><br/>
<a id="privacyCloseBtn" class="privacy" href="#"
onclick="I('privacyPolicy').style.display='none'">Close</a><br/>
</div>
<div><a class="privacy" href="#" onclick="I('privacyPolicy').style.display=''">Privacy</a></div>
</div>
<script type="text/javascript">setPageTitle();</script>
<script type="text/javascript">setTimeout(initUI, 100);</script>
</body>
</html>

42
web/listener.go Normal file
View File

@ -0,0 +1,42 @@
//go:build !linux
// +build !linux
package web
import (
"crypto/tls"
"github.com/go-chi/chi/v5"
"github.com/librespeed/speedtest/config"
log "github.com/sirupsen/logrus"
"net"
"net/http"
)
func startListener(conf *config.Config, r *chi.Mux) error {
var s error
addr := net.JoinHostPort(conf.BindAddress, conf.Port)
log.Infof("Starting backend server on %s", addr)
// TLS and HTTP/2.
if conf.EnableTLS {
log.Info("Use TLS connection.")
if !(conf.EnableHTTP2) {
srv := &http.Server{
Addr: addr,
Handler: r,
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
}
s = srv.ListenAndServeTLS(conf.TLSCertFile, conf.TLSKeyFile)
} else {
s = http.ListenAndServeTLS(addr, conf.TLSCertFile, conf.TLSKeyFile, r)
}
} else {
if conf.EnableHTTP2 {
log.Errorf("TLS is mandatory for HTTP/2. Ignore settings that enable HTTP/2.")
}
s = http.ListenAndServe(addr, r)
}
return s
}

60
web/listener_linux.go Normal file
View File

@ -0,0 +1,60 @@
//go:build linux
// +build linux
package web
import (
"crypto/tls"
"github.com/coreos/go-systemd/v22/activation"
"github.com/go-chi/chi/v5"
"github.com/librespeed/speedtest/config"
log "github.com/sirupsen/logrus"
"net"
"net/http"
)
func startListener(conf *config.Config, r *chi.Mux) error {
// See if systemd socket activation has been used when starting our process
listeners, err := activation.Listeners()
if err != nil {
log.Fatalf("Error whilst checking for systemd socket activation %s", err)
}
var s error
switch len(listeners) {
case 0:
addr := net.JoinHostPort(conf.BindAddress, conf.Port)
log.Infof("Starting backend server on %s", addr)
// TLS and HTTP/2.
if conf.EnableTLS {
log.Info("Use TLS connection.")
if !(conf.EnableHTTP2) {
srv := &http.Server{
Addr: addr,
Handler: r,
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
}
s = srv.ListenAndServeTLS(conf.TLSCertFile, conf.TLSKeyFile)
} else {
s = http.ListenAndServeTLS(addr, conf.TLSCertFile, conf.TLSKeyFile, r)
}
} else {
if conf.EnableHTTP2 {
log.Errorf("TLS is mandatory for HTTP/2. Ignore settings that enable HTTP/2.")
}
s = http.ListenAndServe(addr, r)
}
case 1:
log.Info("Starting backend server on inherited file descriptor via systemd socket activation")
if conf.BindAddress != "" || conf.Port != "" {
log.Errorf("Both an address/port (%s:%s) has been specificed in the config AND externally configured socket activation has been detected", conf.BindAddress, conf.Port)
log.Fatal(`Please deconfigure socket activation (e.g. in systemd unit files), or set both 'bind_address' and 'listen_port' to ''`)
}
s = http.Serve(listeners[0], r)
default:
log.Fatalf("Asked to listen on %d sockets via systemd activation. Sorry we currently only support listening on 1 socket.", len(listeners))
}
return s
}

View File

@ -64,38 +64,37 @@ func ListenAndServe(conf *config.Config) error {
assetFS = justFilesFilesystem{fs: http.Dir(conf.AssetsPath), readDirBatchSize: 2}
}
addr := net.JoinHostPort(conf.BindAddress, conf.Port)
log.Infof("Starting backend server on %s", addr)
r.Get("/*", pages(assetFS))
r.HandleFunc("/empty", empty)
r.HandleFunc("/backend/empty", empty)
r.Get("/garbage", garbage)
r.Get("/backend/garbage", garbage)
r.Get("/getIP", getIP)
r.Get("/backend/getIP", getIP)
r.Get("/results", results.DrawPNG)
r.Get("/results/", results.DrawPNG)
r.Get("/backend/results", results.DrawPNG)
r.Get("/backend/results/", results.DrawPNG)
r.Post("/results/telemetry", results.Record)
r.Post("/backend/results/telemetry", results.Record)
r.HandleFunc("/stats", results.Stats)
r.HandleFunc("/backend/stats", results.Stats)
r.Get(conf.BaseURL+"/*", pages(assetFS, conf.BaseURL))
r.HandleFunc(conf.BaseURL+"/empty", empty)
r.HandleFunc(conf.BaseURL+"/backend/empty", empty)
r.Get(conf.BaseURL+"/garbage", garbage)
r.Get(conf.BaseURL+"/backend/garbage", garbage)
r.Get(conf.BaseURL+"/getIP", getIP)
r.Get(conf.BaseURL+"/backend/getIP", getIP)
r.Get(conf.BaseURL+"/results", results.DrawPNG)
r.Get(conf.BaseURL+"/results/", results.DrawPNG)
r.Get(conf.BaseURL+"/backend/results", results.DrawPNG)
r.Get(conf.BaseURL+"/backend/results/", results.DrawPNG)
r.Post(conf.BaseURL+"/results/telemetry", results.Record)
r.Post(conf.BaseURL+"/backend/results/telemetry", results.Record)
r.HandleFunc(conf.BaseURL+"/stats", results.Stats)
r.HandleFunc(conf.BaseURL+"/backend/stats", results.Stats)
// PHP frontend default values compatibility
r.HandleFunc("/empty.php", empty)
r.HandleFunc("/backend/empty.php", empty)
r.Get("/garbage.php", garbage)
r.Get("/backend/garbage.php", garbage)
r.Get("/getIP.php", getIP)
r.Get("/backend/getIP.php", getIP)
r.Post("/results/telemetry.php", results.Record)
r.Post("/backend/results/telemetry.php", results.Record)
r.HandleFunc("/stats.php", results.Stats)
r.HandleFunc("/backend/stats.php", results.Stats)
r.HandleFunc(conf.BaseURL+"/empty.php", empty)
r.HandleFunc(conf.BaseURL+"/backend/empty.php", empty)
r.Get(conf.BaseURL+"/garbage.php", garbage)
r.Get(conf.BaseURL+"/backend/garbage.php", garbage)
r.Get(conf.BaseURL+"/getIP.php", getIP)
r.Get(conf.BaseURL+"/backend/getIP.php", getIP)
r.Post(conf.BaseURL+"/results/telemetry.php", results.Record)
r.Post(conf.BaseURL+"/backend/results/telemetry.php", results.Record)
r.HandleFunc(conf.BaseURL+"/stats.php", results.Stats)
r.HandleFunc(conf.BaseURL+"/backend/stats.php", results.Stats)
go listenProxyProtocol(conf, r)
return http.ListenAndServe(addr, r)
return startListener(conf, r)
}
func listenProxyProtocol(conf *config.Config, r *chi.Mux) {
@ -114,8 +113,15 @@ func listenProxyProtocol(conf *config.Config, r *chi.Mux) {
}
}
func pages(fs http.FileSystem) http.HandlerFunc {
func pages(fs http.FileSystem, BaseURL string) http.HandlerFunc {
var removeBaseURL *regexp.Regexp
if BaseURL != "" {
removeBaseURL = regexp.MustCompile("^" + BaseURL + "/")
}
fn := func(w http.ResponseWriter, r *http.Request) {
if BaseURL != "" {
r.URL.Path = removeBaseURL.ReplaceAllString(r.URL.Path, "/")
}
if r.RequestURI == "/" {
r.RequestURI = "/index.html"
}