Merge branch 'master' into announcement

pull/587/head
Hunter Long 2020-06-04 03:17:01 -07:00 committed by GitHub
commit eef5e89045
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
91 changed files with 2836 additions and 603 deletions

View File

@ -5,7 +5,9 @@ about: If you're having an issue or see an error
---
### Describe the bug
Try to explain what issue your'e having in detail. You can copy and paste the issue from the log file in your root directory `/logs/statup.log`.
Try to explain what issue your'e having in detail. You can copy and paste the issue from the log file in your root directory `/logs/statping.log`.
You can set the environment variable `ALLOW_REPORTS` to `true` to allow errors to be sent to our error reporting server.
### To Reproduce
Steps to reproduce the behavior:

View File

@ -7,6 +7,8 @@ about: If you're having an issue or see an error
### Describe the bug
A clear and concise description of what the bug is.
You can set the environment variable `ALLOW_REPORTS` to `true` to allow errors to be sent to our error reporting server.
### To Reproduce
Steps to reproduce the behavior:
1. Go to '...'

View File

@ -160,8 +160,7 @@ jobs:
- name: Postman Tests
uses: matt-ball/newman-action@master
with:
postmanApiKey: ${{ secrets.POSTMAN_API }}
collection: 1898229-94807b85-ef65-4370-9144-b1a74e04cb0e
environment: ./dev/postman_environment.json
collection: ./dev/postman.json
environment: ./dev/postman_environment_sqlite.json
timeoutRequest: 15000
delayRequest: 1000
delayRequest: 500

View File

@ -101,7 +101,7 @@ jobs:
env:
VERSION: ${{ env.VERSION }}
run: |
make build
make build certs
chmod +x statping
mv statping $(go env GOPATH)/bin/
@ -143,7 +143,7 @@ jobs:
env:
COVERALLS: ${{ secrets.COVERALLS }}
test-postman:
test-postman-sqlite:
needs: compile
runs-on: ubuntu-latest
steps:
@ -175,17 +175,128 @@ jobs:
- name: Run Statping
run: |
API_SECRET=demosecret123 statping --port=8080 > /dev/null &
API_SECRET=demosecret123 statping --port=8585 > /dev/null &
sleep 3
- name: Postman Tests
- name: Postman SQLite Tests
uses: matt-ball/newman-action@master
with:
postmanApiKey: ${{ secrets.POSTMAN_API }}
collection: 1898229-94807b85-ef65-4370-9144-b1a74e04cb0e
environment: ./dev/postman_environment.json
apiKey: ${{ secrets.POSTMAN_API }}
collection: ./dev/postman.json
environment: ./dev/postman_env_sqlite.json
timeoutRequest: 15000
delayRequest: 1000
delayRequest: 500
test-postman-mysql:
needs: compile
runs-on: ubuntu-latest
services:
mysql:
image: mysql:5.7
env:
MYSQL_ROOT_PASSWORD: password123
MYSQL_DATABASE: statping
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '1.14.2'
- name: Setting ENV's
run: |
echo "::add-path::$(go env GOPATH)/bin"
echo "::add-path::/opt/hostedtoolcache/node/10.20.1/x64/bin"
echo ::set-env name=VERSION::$(cat version.txt)
shell: bash
- name: Download Compiled Frontend (rice-box.go)
uses: actions/download-artifact@v1
with:
name: static-rice-box
path: ./source
- name: Install Statping
env:
VERSION: ${{ env.VERSION }}
run: |
make build
chmod +x statping
mv statping $(go env GOPATH)/bin/
- name: Run Statping
run: |
API_SECRET=demosecret123 statping --port=8585 > /dev/null &
sleep 3
- name: Postman MySQL Tests
uses: matt-ball/newman-action@master
with:
apiKey: ${{ secrets.POSTMAN_API }}
collection: ./dev/postman.json
environment: ./dev/postman_env_mysql.json
timeoutRequest: 15000
delayRequest: 500
test-postman-postgres:
needs: compile
runs-on: ubuntu-latest
services:
postgres:
image: postgres:10.8
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: password123
POSTGRES_DB: statping
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '1.14.2'
- name: Setting ENV's
run: |
echo "::add-path::$(go env GOPATH)/bin"
echo "::add-path::/opt/hostedtoolcache/node/10.20.1/x64/bin"
echo ::set-env name=VERSION::$(cat version.txt)
shell: bash
- name: Download Compiled Frontend (rice-box.go)
uses: actions/download-artifact@v1
with:
name: static-rice-box
path: ./source
- name: Install Statping
env:
VERSION: ${{ env.VERSION }}
run: |
make build
chmod +x statping
mv statping $(go env GOPATH)/bin/
- name: Run Statping
run: |
API_SECRET=demosecret123 statping --port=8585 > /dev/null &
sleep 3
- name: Postman Postgres Tests
uses: matt-ball/newman-action@master
with:
apiKey: ${{ secrets.POSTMAN_API }}
collection: ./dev/postman.json
environment: ./dev/postman_env_postgres.json
timeoutRequest: 15000
delayRequest: 500
build-binaries:
needs: compile
@ -250,7 +361,7 @@ jobs:
path: ./build
upload-release:
needs: [test, test-postman, build-binaries]
needs: [test, test-postman-sqlite, test-postman-mysql, test-postman-postgres, build-binaries]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

View File

@ -8,11 +8,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Stale Issues
uses: actions/stale@v1.1.0
uses: actions/stale@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: "This issue hasn't had any updates in a while. If this is still a problem, please create a new issue."
stale-issue-label: "stale"
days-before-stale: 30
days-before-close: 7
exempt-issue-labels: "bug,urgent,feature,pinned,locked"
exempt-issue-label: "bug,urgent,feature,pinned,locked"

View File

@ -1,3 +1,25 @@
# 0.90.45 (06-01-2020)
- Merged PR [#612](https://github.com/statping/statping/pull/612) for edit/create service issue.
# 0.90.44 (05-25-2020)
- Modified Makefile to include "netgo" tag during golang build
# 0.90.43 (05-21-2020)
- Fixed service TLS checkbox form for edit and create
- Modified ICMP ping's to use system's "ping" command (doesn't need root access)
# 0.90.42 (05-20-2020)
- Fixed TCP services that dont use TLS.
# 0.90.41 (05-20-2020)
- Added TLS Client Cert/Key feature for HTTP and TCP/UDP services
- Replaced environment variable ADMIN_PASS to ADMIN_PASSWORD.
# 0.90.40 (05-18-2020)
- Fixed issues with MySQL and Postgres taking forever to insert sample data (now run in bulk)
- Removed API Authentication for /api/logout route
- Modified Core Sample/Upstart row to include NAME, DESCRIPTION, and DOMAIN environment vars (also added default values)
# 0.90.39 (05-15-2020)
- Modified some SCSS designs for services failures in group
- Fixed Twilio notifier and tests

View File

@ -9,6 +9,7 @@ COPY --from=base /usr/local/bin/sass /usr/local/bin/
COPY --from=base /usr/local/share/ca-certificates /usr/local/share/
WORKDIR /app
VOLUME /app
ENV IS_DOCKER=true
ENV SASS=/usr/local/bin/sass

View File

@ -162,13 +162,13 @@ build-win:
go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-windows-386/statping.exe ./cmd
build-darwin:
GO111MODULE="on" GOOS=darwin GOARCH=amd64 go build -a -ldflags "-s -w -X main.VERSION=${VERSION}" -o releases/statping-darwin-amd64/statping --tags "darwin" ./cmd
GO111MODULE="on" GOOS=darwin GOARCH=amd64 go build -a -ldflags "-s -w -X main.VERSION=${VERSION}" -o releases/statping-darwin-amd64/statping --tags "netgo darwin" ./cmd
build-linux:
CGO_ENABLED=1 GO111MODULE="on" GOOS=linux GOARCH=amd64 \
go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-linux-amd64/statping --tags "linux" ./cmd
go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-linux-amd64/statping --tags "netgo linux" ./cmd
CGO_ENABLED=1 GO111MODULE="on" GOOS=linux GOARCH=386 \
go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-linux-386/statping --tags "linux" ./cmd
go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION}" -o releases/statping-linux-386/statping --tags "netgo linux" ./cmd
build-linux-arm:
CGO_ENABLED=1 CC=arm-linux-gnueabihf-gcc-6 CXX=arm-linux-gnueabihf-g++-6 GO111MODULE="on" GOOS=linux GOARCH=arm GOARM=7 \
@ -322,5 +322,13 @@ postman: clean compile
newman run -e dev/postman_environment.json dev/postman.json
killall statping
certs:
openssl req -newkey rsa:2048 \
-new -nodes -x509 \
-days 3650 \
-out cert.pem \
-keyout key.pem \
-subj "/C=US/ST=California/L=Santa Monica/O=Statping/OU=Development/CN=localhost"
.PHONY: all build build-all build-alpine test-all test test-api docker frontend up down print_details lite sentry-release snapcraft build-linux build-mac build-win build-all postman
.SILENT: travis_s3_creds

View File

@ -285,7 +285,7 @@ func runOnce() error {
func checkGithubUpdates() (githubResponse, error) {
url := "https://api.github.com/repos/statping/statping/releases/latest"
contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(2*time.Second), true)
contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(2*time.Second), true, nil)
if err != nil {
return githubResponse{}, err
}

View File

@ -114,8 +114,9 @@ func start() {
}
if utils.Params.GetBool("SAMPLE_DATA") {
log.Infoln("Adding Sample Data")
if err := configs.TriggerSamples(); err != nil {
exit(errors.Wrap(err, "error creating database"))
exit(errors.Wrap(err, "error adding sample data"))
}
} else {
if err := core.Samples(); err != nil {

View File

@ -104,6 +104,23 @@ type Database interface {
FormatTime(t time.Time) string
ParseTime(t string) (time.Time, error)
DbType() string
GormDB() *gorm.DB
ChunkSize() int
}
func (it *Db) ChunkSize() int {
switch it.Database.Dialect().GetName() {
case "mysql":
return 3000
case "postgres":
return 3000
default:
return 100
}
}
func (it *Db) GormDB() *gorm.DB {
return it.Database
}
func (it *Db) DbType() string {

View File

@ -10,7 +10,7 @@ services:
POSTGRES_DB: statping
POSTGRES_USER: root
ports:
- "127.0.0.1:5432:5432"
- 5432:5432
healthcheck:
test: ["CMD-SHELL", "pg_isready -U root"]
interval: 15s
@ -27,7 +27,7 @@ services:
MYSQL_USER: root
MYSQL_PASSWORD: password123
ports:
- "127.0.0.1:3306:3306"
- 3306:3306
healthcheck:
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
timeout: 20s

View File

@ -1,73 +1,48 @@
version: '2.3'
###############################################
# Statping Full Stack
#
# Statping SQLIte => http://localhost:8080
# Statping MySQL => http://localhost:8081
# Statping Postgres => http://localhost:8082
# Statping MariaDB => http://localhost:8083
#
# Adminer => http://localhost:8282
# Prometheus => http://localhost:7050
# Grafana => http://localhost:3000
#
# MySQL => http://localhost:3306
# Postgres => http://localhost:5432
# MariaDB => http://localhost:3307
#
###############################################
services:
nginx-proxy:
image: jwilder/nginx-proxy
ports:
- 80:80
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
networks:
- statping
# statping_dev:
# container_name: statping_dev
# build:
# context: .
# dockerfile: ./dev/Dockerfile.dev
# args:
# VERSION: DEV
# COMMIT: DEV
# restart: on-failure
# volumes:
# - ./:/go/src/github.com/statping/statping
# environment:
# VIRTUAL_HOST: local.statping.com
# VIRTUAL_PORT: 8888
# GO_ENV: test
# DB_CONN: sqlite
# API_SECRET: exampleapisecret
# NAME: Statping on SQLite
# DOMAIN: http://localhost:4000
# DESCRIPTION: This is a dev environment on SQLite!
# ADMIN_USER: admin
# ADMIN_PASS: admin
# PORT: 8585
# SERVICES: '[{"name": "Local Statping", "type": "http", "domain": "http://localhost:8585", "interval": 30}]'
# ports:
# - 8888:8888
# - 8585:8585
# networks:
# - statping
# healthcheck:
# test: ["CMD-SHELL", "curl -f http://localhost:8585/health || false"]
# timeout: 2s
# interval: 20s
# retries: 30
statping:
container_name: statping
statping_sqlite:
container_name: statping_sqlite
build:
context: ./
context: ../
restart: on-failure
volumes:
- ./docker/statping/sqlite:/app
- ./services.yml:/app/services.yml
environment:
SERVICES: '[{"name": "Local Statping", "type": "http", "domain": "http://localhost:8585", "interval": 30}]'
VIRTUAL_HOST: sqlite.dev.statping.com
VIRTUAL_PORT: 8080
DB_CONN: sqlite
API_SECRET: exampleapisecret
NAME: Statping on SQLite
DOMAIN: http://localhost:4000
NAME: Statping
DOMAIN: http://localhost:8080
DESCRIPTION: This is a dev environment on SQLite!
ADMIN_USER: admin
ADMIN_PASS: admin
ADMIN_PASSWORD: admin
SAMPLE_DATA: 'false'
ports:
- 4000:8080
- 8080:8080
networks:
- statping
- database
- frontend
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || false"]
timeout: 2s
@ -77,88 +52,124 @@ services:
statping_mysql:
container_name: statping_mysql
build:
context: ./
context: ../
restart: on-failure
ports:
- 4005:8080
volumes:
- ./docker/statping/mysql:/app
links:
- mysql
- ./services.yml:/app/services.yml
environment:
VIRTUAL_HOST: mysql.dev.statping.com
VIRTUAL_PORT: 8080
DB_CONN: mysql
DB_HOST: mysql
DB_PORT: 3306
DB_DATABASE: statping
DB_USER: root
DB_PASS: password123
DB_DATABASE: statping
DB_PORT: 3306
API_SECRET: exampleapisecret
NAME: Statping on MySQL
DOMAIN: http://localhost:4005
NAME: Statping MySQL
DOMAIN: http://localhost:8081
DESCRIPTION: This is a dev environment on MySQL!
ADMIN_USER: admin
ADMIN_PASS: admin
ADMIN_PASSWORD: admin
SAMPLE_DATA: 'false'
ports:
- 8081:8080
networks:
- statping
depends_on:
mysql:
condition: service_healthy
- database
- frontend
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || false"]
timeout: 2s
interval: 10s
retries: 20
depends_on:
mysql:
condition: service_healthy
statping_postgres:
container_name: statping_postgres
build:
context: ./
context: ../
restart: on-failure
ports:
- 4010:8080
volumes:
- ./docker/statping/postgres:/app
links:
- postgres
- ./docker/statping/mysql:/app
- ./services.yml:/app/services.yml
environment:
VIRTUAL_HOST: postgres.dev.statping.com
VIRTUAL_PORT: 8080
DB_CONN: postgres
DB_HOST: postgres
DB_PORT: 5432
DB_DATABASE: statping
DB_USER: root
DB_PASS: password123
DB_DATABASE: statping
DB_PORT: 5432
API_SECRET: exampleapisecret
NAME: Statping on Postgres
DOMAIN: http://localhost:4010
NAME: Statping Postgres
DOMAIN: http://localhost:8082
DESCRIPTION: This is a dev environment on Postgres!
ADMIN_USER: admin
ADMIN_PASS: admin
ADMIN_PASSWORD: admin
SAMPLE_DATA: 'false'
ports:
- 8082:8080
networks:
- statping
- database
- frontend
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || false"]
timeout: 2s
interval: 10s
retries: 20
depends_on:
postgres:
condition: service_healthy
statping_mariadb:
container_name: statping_mariadb
build:
context: ../
restart: on-failure
volumes:
- ./docker/statping/mariadb:/app
- ./services.yml:/app/services.yml
environment:
DB_CONN: mysql
DB_HOST: mariadb
DB_USER: root
DB_PASS: password123
DB_DATABASE: statping
DB_PORT: 3306
API_SECRET: exampleapisecret
NAME: Statping MariaDB
DOMAIN: http://localhost:8083
DESCRIPTION: This is a dev environment on MariaDB!
ADMIN_USER: admin
ADMIN_PASSWORD: admin
SAMPLE_DATA: 'false'
ports:
- 8083:8080
networks:
- database
- frontend
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || false"]
timeout: 5s
interval: 5s
retries: 30
timeout: 2s
interval: 10s
retries: 20
depends_on:
mariadb:
condition: service_healthy
postgres:
container_name: postgres
image: postgres
volumes:
- ./docker/databases/postgres:/var/lib/postgresql/data
ports:
- 5432:5432
environment:
POSTGRES_PASSWORD: password123
POSTGRES_DB: statping
POSTGRES_USER: root
networks:
- statping
- database
healthcheck:
test: ["CMD-SHELL", "pg_isready -U root"]
interval: 15s
@ -175,95 +186,59 @@ services:
MYSQL_ROOT_PASSWORD: password123
MYSQL_DATABASE: statping
MYSQL_USER: root
MYSQL_PASSWORD: password
MYSQL_PASSWORD: password123
ports:
- 3306:3306
networks:
- statping
- database
healthcheck:
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
timeout: 20s
interval: 15s
retries: 30
phpmyadmin:
container_name: phpmyadmin
image: phpmyadmin/phpmyadmin
restart: on-failure
depends_on:
mysql:
condition: service_healthy
ports:
- 5050:80
links:
- mysql:db
environment:
VIRTUAL_HOST: phpmyadmin.statping.com
VIRTUAL_PORT: 80
MYSQL_ROOT_PASSWORD: password123
PMA_HOST: mysql
PMA_USER: root
PMA_PASSWORD: password123
PMA_PORT: 3306
networks:
- statping
sqlite-web:
container_name: sqlite-web
image: coleifer/sqlite-web
restart: on-failure
command: sqlite_web -H 0.0.0.0 -r -x /data/statping.db
depends_on:
statping:
condition: service_healthy
ports:
- 6050:8080
links:
- statping
mariadb:
container_name: mariadb
image: mariadb
volumes:
- ./docker/statping/sqlite/statping.db:/data/statping.db:ro
- ./docker/databases/mariadb:/var/lib/mysql
restart: always
environment:
VIRTUAL_HOST: sqladmin.statping.com
VIRTUAL_PORT: 8080
SQLITE_DATABASE: /data/statping.db
networks:
- statping
pgadmin4:
container_name: pgadmin4
image: fenglc/pgadmin4
restart: on-failure
environment:
VIRTUAL_HOST: pgadmin.statping.com
VIRTUAL_PORT: 5050
DEFAULT_USER: admin@admin.com
DEFAULT_PASSWORD: admin
depends_on:
postgres:
condition: service_healthy
MYSQL_ROOT_PASSWORD: password123
MYSQL_DATABASE: statping
MYSQL_USER: root
MYSQL_PASSWORD: password123
ports:
- 7000:5050
links:
- postgres:postgres
- 3307:3306
networks:
- statping
- database
healthcheck:
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
timeout: 20s
interval: 15s
retries: 30
adminer:
container_name: adminer
image: adminer
restart: always
networks:
- database
- frontend
ports:
- 8282:8080
prometheus:
container_name: prometheus
image: prom/prometheus:v2.0.0
restart: on-failure
volumes:
- ./dev/prometheus.yml:/etc/prometheus/prometheus.yml
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- ./docker/databases/prometheus:/prometheus
links:
- statping
- statping_mysql
- statping_postgres
ports:
- 7050:9090
networks:
- statping
environment:
VIRTUAL_HOST: prometheus.statping.com
VIRTUAL_PORT: 9090
- database
healthcheck:
test: "/bin/wget -q -Y off http://localhost:9090/status -O /dev/null > /dev/null 2>&1"
interval: 10s
@ -277,25 +252,23 @@ services:
- 3000:3000
volumes:
- ./docker/grafana:/var/lib/grafana
- ./dev/grafana/datasource.yml:/etc/grafana/provisioning/datasources/datasource.yml
- ./dev/grafana/dashboard.yml:/etc/grafana/provisioning/dashboards/dashboard.yml
- ./dev/grafana/statping_dashboard.json:/etc/grafana/provisioning/dashboards/statping_dashboard.json
- ./grafana/datasource.yml:/etc/grafana/provisioning/datasources/datasource.yml
- ./grafana/dashboard.yml:/etc/grafana/provisioning/dashboards/dashboard.yml
- ./grafana/statping_dashboard.json:/etc/grafana/provisioning/dashboards/statping_dashboard.json
environment:
- VIRTUAL_HOST=grafana.statping.com
- VIRTUAL_PORT=3000
- GF_USERS_ALLOW_SIGN_UP=false
- GF_AUTH_ANONYMOUS_ENABLED=true
depends_on:
prometheus:
condition: service_healthy
links:
- prometheus
networks:
- statping
- frontend
- database
healthcheck:
test: "/usr/bin/wget -q -Y off http://localhost:3000/api/health -O /dev/null > /dev/null 2>&1"
interval: 10s
retries: 20
networks:
statping:
frontend:
database:

View File

@ -24,7 +24,7 @@ services:
DOMAIN: http://localhost:8585
DESCRIPTION: This is a dev environment with auto reloading!
ADMIN_USER: admin
ADMIN_PASS: admin
ADMIN_PASSWORD: admin
PORT: 8585
ports:
- 8888:8888

View File

@ -1,7 +1,10 @@
[users]
# disable user signup / registration
allow_sign_up = false
viewers_can_edit = true
editors_can_admin = true
[auth.anonymous]
# enable anonymous access
enabled = true

View File

@ -558,24 +558,14 @@
"templating": {
"list": [
{
"allValue": null,
"current": {},
"datasource": "${DS_PROMETHEUS}",
"hide": 0,
"includeAll": false,
"label": "service",
"multi": false,
"name": "service",
"label": "datasource",
"name": "DS_PROMETHEUS",
"options": [],
"query": "label_values(statup_service_latency, name)",
"query": "prometheus",
"refresh": 1,
"regex": "",
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
"type": "datasource"
}
]
},

1477
dev/postman.json vendored

File diff suppressed because one or more lines are too long

49
dev/postman_env_mysql.json vendored Normal file
View File

@ -0,0 +1,49 @@
{
"id": "0ff1dcd6-54f3-44a7-9c18-cc3c8e7df357",
"name": "Local Statping",
"values": [
{
"key": "endpoint",
"value": "http://0.0.0.0:8585",
"enabled": true
},
{
"key": "api_key",
"value": "demosecret123",
"enabled": true
},
{
"key": "db_connection",
"value": "mysql",
"enabled": true
},
{
"key": "db_host",
"value": "localhost",
"enabled": true
},
{
"key": "db_user",
"value": "root",
"enabled": true
},
{
"key": "db_password",
"value": "password123",
"enabled": true
},
{
"key": "db_database",
"value": "statping",
"enabled": true
},
{
"key": "db_port",
"value": "3306",
"enabled": true
}
],
"_postman_variable_scope": "environment",
"_postman_exported_at": "2020-05-18T20:44:05.571Z",
"_postman_exported_using": "Postman/7.24.0"
}

49
dev/postman_env_postgres.json vendored Normal file
View File

@ -0,0 +1,49 @@
{
"id": "0ff1dcd6-54f3-44a7-9c18-cc3c8e7df357",
"name": "Local Statping",
"values": [
{
"key": "endpoint",
"value": "http://0.0.0.0:8585",
"enabled": true
},
{
"key": "api_key",
"value": "demosecret123",
"enabled": true
},
{
"key": "db_connection",
"value": "postgres",
"enabled": true
},
{
"key": "db_host",
"value": "localhost",
"enabled": true
},
{
"key": "db_user",
"value": "root",
"enabled": true
},
{
"key": "db_password",
"value": "password123",
"enabled": true
},
{
"key": "db_database",
"value": "statping",
"enabled": true
},
{
"key": "db_port",
"value": "5432",
"enabled": true
}
],
"_postman_variable_scope": "environment",
"_postman_exported_at": "2020-05-18T20:44:05.571Z",
"_postman_exported_using": "Postman/7.24.0"
}

49
dev/postman_env_sqlite.json vendored Normal file
View File

@ -0,0 +1,49 @@
{
"id": "0ff1dcd6-54f3-44a7-9c18-cc3c8e7df357",
"name": "Local Statping",
"values": [
{
"key": "endpoint",
"value": "http://0.0.0.0:8585",
"enabled": true
},
{
"key": "api_key",
"value": "demosecret123",
"enabled": true
},
{
"key": "db_connection",
"value": "sqlite",
"enabled": true
},
{
"key": "db_host",
"value": "localhost",
"enabled": true
},
{
"key": "db_user",
"value": "root",
"enabled": true
},
{
"key": "db_password",
"value": "password123",
"enabled": true
},
{
"key": "db_database",
"value": "statping",
"enabled": true
},
{
"key": "db_port",
"value": "3306",
"enabled": true
}
],
"_postman_variable_scope": "environment",
"_postman_exported_at": "2020-05-18T20:44:05.571Z",
"_postman_exported_using": "Postman/7.24.0"
}

View File

@ -1,21 +0,0 @@
{
"id": "0ff1dcd6-54f3-44a7-9c18-cc3c8e7df357",
"name": "Local Statping",
"values": [
{
"key": "endpoint",
"value": "http://127.0.0.1:8080",
"description": "",
"enabled": true
},
{
"key": "api_key",
"value": "demosecret123",
"description": "",
"enabled": true
}
],
"_postman_variable_scope": "environment",
"_postman_exported_at": "2018-11-17T16:55:15.031Z",
"_postman_exported_using": "Postman/6.5.2"
}

22
dev/prometheus.yml vendored
View File

@ -3,17 +3,11 @@ global:
evaluation_interval: 15s
scrape_configs:
- job_name: 'statping_local'
scrape_interval: 15s
bearer_token: 'samplesecret'
static_configs:
- targets: ['docker0:8585']
- job_name: 'statping'
- job_name: 'statping_sqlite'
scrape_interval: 15s
bearer_token: 'exampleapisecret'
static_configs:
- targets: ['statping:8080']
- targets: ['statping_sqlite:8080']
- job_name: 'statping_mysql'
scrape_interval: 15s
@ -21,14 +15,14 @@ scrape_configs:
static_configs:
- targets: ['statping_mysql:8080']
- job_name: 'statping_mariadb'
scrape_interval: 15s
bearer_token: 'exampleapisecret'
static_configs:
- targets: ['statping_mariadb:8080']
- job_name: 'statping_postgres'
scrape_interval: 15s
bearer_token: 'exampleapisecret'
static_configs:
- targets: ['statping_postgres:8080']
- job_name: 'statping_dev'
scrape_interval: 15s
bearer_token: 'exampleapisecret'
static_configs:
- targets: ['statping_dev:8585']

2
dev/pwd-stack.yml vendored
View File

@ -18,7 +18,7 @@ services:
DOMAIN: http://localhost:8080
DESCRIPTION: This is a dev environment on SQLite!
ADMIN_USER: admin
ADMIN_PASS: admin
ADMIN_PASSWORD: admin
postgres:
hostname: postgres

View File

@ -22,7 +22,12 @@ const webpackConfig = {
{
test: /\.vue$/,
loader: 'vue-loader',
include: [ helpers.root('src') ]
include: [ helpers.root('src') ],
options: {
loaders: {
i18n: '@kazupon/vue-i18n-loader'
}
}
},
{
test: /\.js$/,

View File

@ -36,6 +36,7 @@
"vue-cookies": "^1.7.0",
"vue-flatpickr-component": "^8.1.5",
"vue-github-button": "^1.1.2",
"vue-i18n": "^8.18.1",
"vue-moment": "^4.1.0",
"vue-observe-visibility": "^0.4.6",
"vue-router": "~3.0",
@ -51,6 +52,7 @@
"@babel/plugin-syntax-import-meta": "~7.2",
"@babel/polyfill": "~7.2",
"@babel/preset-env": "^7.9.0",
"@kazupon/vue-i18n-loader": "^0.5.0",
"@vue/babel-preset-app": "^4.1.2",
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-service": "^4.2.3",

View File

@ -7,14 +7,13 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<meta name="description" content="{{CoreApp.Description}}">
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
<base href="{{BasePath}}">
{{if USE_CDN}}
<link rel="shortcut icon" type="image/x-icon" href="https://assets.statping.com/favicon.ico">
<link rel="stylesheet" href="https://assets.statping.com/vendor.css">
<link rel="stylesheet" href="https://assets.statping.com/style.css">
<link rel="stylesheet" href="https://assets.statping.com/main.css">
{{else}}
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
{{if USING_ASSETS}}
<link href="css/vendor.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -5,6 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, user-scalable=0">
<base href="/">
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<title>Statping</title>
</head>
<body>

View File

@ -6,7 +6,6 @@
</template>
<script>
import Api from './API';
import Footer from "./components/Index/Footer";
export default {
@ -28,6 +27,9 @@
async beforeMount() {
await this.$store.dispatch('loadCore')
this.$i18n.locale = this.core.language || "en";
// this.$i18n.locale = "ru";
if (!this.core.setup) {
this.$router.push('/setup')
}

View File

@ -6,6 +6,13 @@
background-color: $sm-background-color;
}
.service-tm-menu {
height: 300px;
}
.service-tm-menu A {
height: 27px;
}
.index-chart {
height: 380px;

View File

@ -67,6 +67,8 @@
this.edit = v
},
editUser(u, mode) {
delete(u.password)
delete(u.confirm_password)
this.user = u
this.edit = !mode
},

View File

@ -1,23 +1,47 @@
<template>
<div class="col-12">
<FormService :in_service="service"/>
<div v-if='ready'>
<div v-if='errorCode==404' class="col-12">
<div class="alert alert-warning" role="alert">
Service {{ this.$route.params.id }} not found!
<router-link v-if="$store.state.admin" to="/dashboard/create_service" class="btn btn-sm btn-outline-success float-right">
<font-awesome-icon icon="plus"/> Create One?
</router-link>
</div>
</div>
<div v-else-if='errorCode==401' class="col-12">
<div class="alert alert-danger" role="alert">
Unauthorized! Perhaps your session has expired?
</div>
</div>
<div v-else class="col-12">
<FormService :in_service="service"/>
</div>
</div>
<div v-else>
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
</template>
<script>
import FormGroup from "../../forms/Group";
import Api from "../../API";
import ToggleSwitch from "../../forms/ToggleSwitch";
import draggable from 'vuedraggable'
import FormService from "../../forms/Service";
import Api from "@/API";
import FormService from "@/forms/Service";
export default {
name: 'EditService',
components: {
FormService,
ToggleSwitch,
FormGroup,
draggable
},
props: {
@ -25,24 +49,100 @@
data () {
return {
ready: false,
service: {}
errorCode: 'none',
service: {
name: "",
type: "http",
domain: "",
group_id: 0,
method: "GET",
post_data: "",
headers: "",
expected: "",
expected_status: 200,
port: 80,
check_interval: 60,
timeout: 15,
permalink: "",
order: 1,
verify_ssl: true,
redirect: true,
allow_notifications: true,
notify_all_changes: true,
notify_after: 2,
public: true,
tls_cert: "",
tls_cert_key: "",
tls_cert_root: "",
},
}
},
computed: {
// because route changes within the same component are re-used
watch: {
$route(to, from) {
this.errorCode = 'none';
this.ready = true;
}
},
async beforeCreate() {
// beforeCreated() causes sync issues with mounted() as they are executed
// one after the other regardless of async/await methods inside.
mounted() {
const id = this.$route.params.id
if (id) {
this.service = await Api.service(id)
}
this.ready = true
},
beforeMount() {
this.loadService(id);
} else {
this.errorCode = 'none';
this.ready = true;
};
},
methods: {
async loadService(id){
this.ready = false;
// api still responds if session is invalid
// specifically, if statping is restarted and an existing session exists
// theres a further check to not display the data in the form component it seems ??
await Api.service(id).then(
response => {
this.service = response;
this.errorCode = 'none';
this.ready = true;
},
error => {
const respStatus = error.response.status;
if ( respStatus == '404' ) {
this.errorCode = 404;
} else if ( respStatus == '401' ) {
this.errorCode = 401 ;
} else {
this.errorCode = 'none';
};
this.ready = true;
}
);
}
}
}
</script>

View File

@ -6,11 +6,11 @@
</div>
</div>
<div class="row mt-2">
<div class="col-4 text-left font-2 text-muted">30 Days Ago</div>
<div class="col-4 text-center font-2" :class="{'text-muted': service.online, 'text-danger': !service.online}">
<div class="col-3 text-left font-2 text-muted">30 Days Ago</div>
<div class="col-6 text-center font-2" :class="{'text-muted': service.online, 'text-danger': !service.online}">
{{service_txt}}
</div>
<div class="col-4 text-right font-2 text-muted">Today</div>
<div class="col-3 text-right font-2 text-muted">Today</div>
</div>
</div>
</template>
@ -36,14 +36,7 @@ export default {
},
computed: {
service_txt() {
const s = this.service
if (!s.online) {
if (!this.toUnix(this.parseISO(s.last_success))) {
return `Always Offline`
}
return `Offline for ${this.ago(s.last_success)}`
}
return `${this.service.online_24_hours}% Uptime`
return this.smallText(this.service)
}
},
mounted () {

View File

@ -223,18 +223,6 @@ export default {
this.stats.low_ping.chart = pingData.chart;
this.stats.low_ping.value = this.humanTime(pingData.low);
},
smallText(s) {
const incidents = s.incidents
if (s.online) {
return `Checked ${this.ago(s.last_success)} ago`
} else {
const last = s.last_failure
if (last) {
return `Offline, last error: ${last} ${this.ago(last.created_at)}`
}
return `Service has been offline for ${this.ago(s.last_success)}`
}
},
visibleChart(isVisible, entry) {
if (isVisible && !this.visible) {
this.visible = true

View File

@ -31,6 +31,18 @@
<small class="form-text text-muted">HTML is allowed inside the footer</small>
</div>
<div class="form-group">
<label>Language</label>
<select v-model="core.language" class="form-control">
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="ru">Russian</option>
<option value="de">German</option>
</select>
<small class="form-text text-muted">HTML is allowed inside the footer</small>
</div>
<div class="form-group row mt-3">
<label class="col-sm-10 col-form-label">Enable Error Reporting</label>
<div class="col-sm-2 float-right">

View File

@ -51,7 +51,7 @@
<label for="gh_callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8">
<div class="input-group">
<input v-bind:value="`${core.domain}/api/oauth/github`" type="text" class="form-control" id="gh_callback" readonly>
<input v-bind:value="`${core.domain}/oauth/github`" type="text" class="form-control" id="gh_callback" readonly>
<div class="input-group-append copy-btn">
<button @click.prevent="copy(`${core.domain}/oauth/github`)" class="btn btn-outline-secondary" type="button">Copy</button>
</div>
@ -90,7 +90,7 @@
<label for="google_callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8">
<div class="input-group">
<input v-bind:value="`${core.domain}/api/oauth/google`" type="text" class="form-control" id="google_callback" readonly>
<input v-bind:value="`${core.domain}/oauth/google`" type="text" class="form-control" id="google_callback" readonly>
<div class="input-group-append copy-btn">
<button @click.prevent="copy(`${core.domain}/oauth/google`)" class="btn btn-outline-secondary" type="button">Copy</button>
</div>
@ -136,7 +136,7 @@
<label for="slack_callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8">
<div class="input-group">
<input v-bind:value="`${core.domain}/api/oauth/slack`" type="text" class="form-control" id="slack_callback" readonly>
<input v-bind:value="`${core.domain}/oauth/slack`" type="text" class="form-control" id="slack_callback" readonly>
<div class="input-group-append copy-btn">
<button @click.prevent="copy(`${core.domain}/oauth/slack`)" class="btn btn-outline-secondary" type="button">Copy</button>
</div>

View File

@ -1,5 +1,5 @@
<template>
<form @submit.prevent="saveService">
<form v-if="service.type" @submit.prevent="saveService">
<div class="card contain-card text-black-50 bg-white mb-4">
<div class="card-header">Service Information</div>
<div class="card-body">
@ -54,18 +54,21 @@
<div class="form-group row">
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval</label>
<div class="col-sm-8">
<div class="col-sm-6">
<span class="slider-info">{{secondsHumanize(service.check_interval)}}</span>
<input v-model="service.check_interval" type="range" class="slider" id="service_interval" min="1" max="1800" :step="stepVal(service.check_interval)">
<input v-model="service.check_interval" type="range" class="slider" id="service_interval" min="1" max="1800" :step="1">
<small id="interval" class="form-text text-muted">Interval to check your service state</small>
</div>
<div class="col-sm-2">
<input v-model="service.check_interval" type="text" name="check_interval" class="form-control">
</div>
</div>
</div>
</div>
<div class="card contain-card text-black-50 bg-white mb-4">
<div class="card-header">{{service.type.toUpperCase()}} Request Details</div>
<div class="card-header">Request Details</div>
<div class="card-body">
<div class="form-group row">
@ -77,7 +80,7 @@
</div>
<div v-if="service.type.match(/^(tcp|udp|grpc)$/)" class="form-group row">
<label class="col-sm-4 col-form-label">{{service.type.toUpperCase()}} Port</label>
<label class="col-sm-4 col-form-label">Port</label>
<div class="col-sm-8">
<input v-model="service.port" type="number" name="port" class="form-control" id="service_port" placeholder="8080">
</div>
@ -99,11 +102,16 @@
<div class="form-group row">
<label class="col-sm-4 col-form-label">Request Timeout</label>
<div class="col-sm-8">
<span class="slider-info">{{secondsHumanize(service.timeout)}}</span>
<div class="col-sm-6">
<span v-if="service.timeout >= 0" class="slider-info">{{secondsHumanize(service.timeout)}}</span>
<input v-model="service.timeout" type="range" id="timeout" name="timeout" class="slider" min="1" max="180">
<small class="form-text text-muted">If the endpoint does not respond within this time it will be considered to be offline</small>
</div>
<div class="col-sm-2">
<input v-model="service.timeout" type="text" name="service_timeout" class="form-control">
</div>
</div>
<div v-if="service.type.match(/^(http)$/) && service.method.match(/^(POST|PATCH|DELETE|PUT)$/)" class="form-group row">
@ -156,6 +164,41 @@
</div>
</div>
<div v-if="service.type.match(/^(tcp|http)$/)" class="form-group row">
<label class="col-sm-4 col-form-label">Use TLS Certificate</label>
<div class="col-8 mt-1">
<span @click="use_tls = !!use_tls" class="switch float-left">
<input v-model="use_tls" type="checkbox" name="verify_ssl-option" class="switch" id="switch-use-tls" v-bind:checked="use_tls">
<label for="switch-use-tls" v-if="use_tls">Custom TLS Certificates for mTLS services</label>
<label for="switch-use-tls" v-if="!use_tls">Ignore TLS Certificates</label>
</span>
</div>
</div>
<div v-if="use_tls" class="form-group row">
<label for="service_tls_cert" class="col-sm-4 col-form-label">TLS Client Certificate</label>
<div class="col-sm-8">
<textarea v-model="service.tls_cert" name="tls_cert" class="form-control" id="service_tls_cert"></textarea>
<small class="form-text text-muted">Absolute path to TLS Client Certificate file or in PEM format</small>
</div>
</div>
<div v-if="use_tls" class="form-group row">
<label for="service_tls_cert_key" class="col-sm-4 col-form-label">TLS Client Key</label>
<div class="col-sm-8">
<textarea v-model="service.tls_cert_key" name="tls_cert_key" class="form-control" id="service_tls_cert_key"></textarea>
<small class="form-text text-muted">Absolute path to TLS Client Key file or in PEM format</small>
</div>
</div>
<div v-if="use_tls" class="form-group row">
<label for="service_tls_cert_chain" class="col-sm-4 col-form-label">Root CA</label>
<div class="col-sm-8">
<textarea v-model="service.tls_cert_root" name="tls_cert_key" class="form-control" id="service_tls_cert_chain"></textarea>
<small class="form-text text-muted">Absolute path to Root CA file or in PEM format (optional)</small>
</div>
</div>
</div>
</div>
@ -235,7 +278,11 @@
notify_all_changes: true,
notify_after: 2,
public: true,
tls_cert: "",
tls_cert_key: "",
tls_cert_root: "",
},
use_tls: false,
groups: [],
}
},
@ -245,17 +292,28 @@
}
},
watch: {
in_service () {
this.service = this.in_service
in_service: function(svr) {
this.service = svr
this.use_tls = svr.tls_cert
}
},
async mounted () {
if (!this.$store.getters.groups) {
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
const groups = await Api.groups()
this.$store.commit('setGroups', groups)
}
this.update()
},
methods: {
created () {
this.update()
},
methods: {
update() {
if (this.in_service) {
this.service = this.in_service
}
this.use_tls = this.service.tls_cert
},
updatePermalink() {
const a = 'àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìłḿñńǹňôöòóœøōõőṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·/_,:;'
const b = 'aaaaaaaaaacccddeeeeeeeegghiiiiiilmnnnnoooooooooprrsssssttuuuuuuuuuwxyyzzz------'

View File

@ -9,7 +9,17 @@
<div class="row">
<div class="col-6">
<div class="form-group">
<label>Database Connection</label>
<label>{{ $t('setup.language') }}</label>
<select @change="changeLanguages" v-model="setup.language" id="language" class="form-control">
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="ru">Russian</option>
<option value="de">German</option>
</select>
</div>
<div class="form-group">
<label>{{ $t('setup.connection') }}</label>
<select @change="canSubmit" v-model="setup.db_connection" id="db_connection" class="form-control">
<option value="sqlite">SQLite</option>
<option value="postgres">Postgres</option>
@ -17,23 +27,23 @@
</select>
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
<label>Host</label>
<label>{{ $t('setup.host') }}</label>
<input @keyup="canSubmit" v-model="setup.db_host" id="db_host" type="text" class="form-control" placeholder="localhost">
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
<label>Database Port</label>
<label>{{ $t('port') }}</label>
<input @keyup="canSubmit" v-model="setup.db_port" id="db_port" type="text" class="form-control" placeholder="localhost">
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
<label>Username</label>
<label>{{ $t('username') }}</label>
<input @keyup="canSubmit" v-model="setup.db_user" id="db_user" type="text" class="form-control" placeholder="root">
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
<label for="db_password">Password</label>
<label for="db_password">{{ $t('password') }}</label>
<input @keyup="canSubmit" v-model="setup.db_password" id="db_password" type="password" class="form-control" placeholder="password123">
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
<label for="db_database">Database</label>
<label for="db_database">{{ $t('database') }}</label>
<input @keyup="canSubmit" v-model="setup.db_database" id="db_database" type="text" class="form-control" placeholder="Database name">
</div>
@ -42,35 +52,51 @@
<div class="col-6">
<div class="form-group">
<label>Project Name</label>
<label>{{ $t('setup.project_name') }}</label>
<input @keyup="canSubmit" v-model="setup.project" id="project" type="text" class="form-control" placeholder="Great Uptime" required>
</div>
<div class="form-group">
<label>Project Description</label>
<label>{{ $t('setup.project_description') }}</label>
<input @keyup="canSubmit" v-model="setup.description" id="description" type="text" class="form-control" placeholder="Great Uptime">
</div>
<div class="form-group">
<label for="domain">Domain URL</label>
<label for="domain">{{ $t('setup.domain') }}</label>
<input @keyup="canSubmit" v-model="setup.domain" type="text" class="form-control" id="domain" required>
</div>
<div class="form-group">
<label>Admin Username</label>
<label>{{ $t('setup.username') }}</label>
<input @keyup="canSubmit" v-model="setup.username" id="username" type="text" class="form-control" placeholder="admin" required>
</div>
<div class="form-group">
<label>Admin Password</label>
<label>{{ $t('setup.username') }}</label>
<input @keyup="canSubmit" v-model="setup.password" id="password" type="password" class="form-control" placeholder="password" required>
</div>
<div class="form-group">
<label>Confirm Admin Password</label>
<label>{{ $t('setup.password_confirm') }}</label>
<input @keyup="canSubmit" v-model="setup.confirm_password" id="password_confirm" type="password" class="form-control" placeholder="password" required>
</div>
<div class="form-group">
<div class="row">
<div class="col-8">
<label>{{ $t('email') }}</label>
<input @keyup="canSubmit" v-model="setup.email" id="email" type="text" class="form-control" placeholder="myemail@domain.com">
</div>
<div class="col-4">
<label class="d-none d-sm-block">{{ $t('setup.newsletter') }}</label>
<span @click="setup.newsletter = !!setup.newsletter" class="switch">
<input v-model="setup.newsletter" type="checkbox" name="using_cdn" class="switch" id="send_newsletter" :checked="setup.newsletter">
<label for="send_newsletter"></label>
</span>
</div>
</div>
<small>{{ $t('setup.newsletter_note') }}</small>
</div>
</div>
<div v-if="error" class="col-12 alert alert-danger">
@ -99,6 +125,7 @@
loading: false,
disabled: true,
setup: {
language: "en",
db_connection: "sqlite",
db_host: "",
db_port: "",
@ -111,7 +138,9 @@
username: "",
password: "",
confirm_password: "",
sample_data: true
sample_data: true,
newsletter: true,
email: "",
}
}
},
@ -128,6 +157,9 @@
this.setup.domain = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":"+window.location.port : "")
},
methods: {
changeLanguages() {
this.$i18n.locale = this.setup.language
},
canSubmit() {
this.error = null
const s = this.setup

View File

@ -82,10 +82,7 @@
},
watch: {
in_user() {
let u = this.in_user
u.password = null
u.password_confirm = null
this.user = u
this.user = this.in_user
}
},
methods: {

View File

@ -0,0 +1,33 @@
const english = {
setup: {
language: "Language",
connection: "Database Connection",
host: "Host",
database: "Database",
project_name: "Project Name",
project_description: "Project Description",
domain: "Domain URL",
username: "Admin Username",
password: "Admin Username",
password_confirm: "Confirm Admin Username",
newsletter: "Newsletter",
newsletter_note: "We will not share your email, emails are only for major updates.",
},
email: "Email Address",
port: "Database Port",
username: "Username",
password: 'password',
service: 'service',
online: 'online',
offline: 'offline',
incident: 'incident',
group: 'group',
message: 'message',
logout: 'logout',
sample_data: 'Sample Data',
today: 'Today',
day: 'day',
hour: 'hour',
}
export default english

View File

@ -0,0 +1,19 @@
const french = {
setup: {
language: "Langue",
database_connection: "Database Connection"
},
service: 'service',
online: 'online',
offline: 'offline',
incident: 'incident',
group: 'group',
message: 'message',
logout: 'logout',
sample_data: 'Sample Data',
today: 'Today',
day: 'day',
hour: 'hour',
}
export default french

View File

@ -0,0 +1,19 @@
const german = {
setup: {
language: "Sprache",
database_connection: "Database Connection"
},
service: 'service',
online: 'online',
offline: 'offline',
incident: 'incident',
group: 'group',
message: 'message',
logout: 'logout',
sample_data: 'Sample Data',
today: 'Today',
day: 'day',
hour: 'hour',
}
export default german

View File

@ -0,0 +1,15 @@
import english from "./english"
import spanish from "./spanish"
import german from "./german"
import russian from "./russian";
import french from "./french";
const language = {
en: english,
es: spanish,
de: german,
ru: russian,
fr: french,
}
export default language

View File

@ -0,0 +1,19 @@
const russian = {
setup: {
language: "язык",
database_connection: "Database Connection"
},
service: 'service',
online: 'online',
offline: 'offline',
incident: 'incident',
group: 'group',
message: 'message',
logout: 'logout',
sample_data: 'Sample Data',
today: 'Today',
day: 'day',
hour: 'hour',
}
export default russian

View File

@ -0,0 +1,19 @@
const spanish = {
setup: {
language: "Idioma",
database_connection: "Database Connection"
},
service: 'service',
online: 'online',
offline: 'offline',
incident: 'incident',
group: 'group',
message: 'message',
logout: 'logout',
sample_data: 'Sample Data',
today: 'Today',
day: 'day',
hour: 'hour',
}
export default spanish

View File

@ -4,9 +4,13 @@ import VueApexCharts from 'vue-apexcharts'
import VueObserveVisibility from 'vue-observe-visibility'
import VueClipboard from 'vue-clipboard2'
import VueCookies from 'vue-cookies'
import VueI18n from 'vue-i18n'
import router from './routes'
import "./mixin"
import "./icons"
import App from '@/App.vue'
import store from './store'
import language from './languages'
Vue.component('apexchart', VueApexCharts)
@ -14,16 +18,19 @@ Vue.use(VueClipboard);
Vue.use(VueRouter);
Vue.use(VueObserveVisibility);
Vue.use(VueCookies);
Vue.use(VueI18n);
const i18n = new VueI18n({
fallbackLocale: "en",
messages: language
});
Vue.$cookies.config('3d')
import router from './routes'
import "./mixin"
import "./icons"
Vue.config.productionTip = false
new Vue({
router,
store,
i18n,
render: h => h(App),
}).$mount('#app')

View File

@ -19,17 +19,9 @@ export default Vue.mixin({
startToday() {
return startOfToday()
},
secondsHumanize (val) {
const t2 = addSeconds(new Date(0), val)
if (val >= 60) {
let minword = "minute"
if (val >= 120) {
minword = "minutes"
}
return format(t2, "m '"+minword+"' s 'seconds'")
}
return format(t2, "s 'seconds'")
},
secondsHumanize (val) {
return `${val} seconds`
},
utc(val) {
return new Date.UTC(val)
},
@ -51,8 +43,26 @@ export default Vue.mixin({
niceDate(val) {
return format(parseISO(val), "EEEE, MMM do h:mma")
},
parseISO(v) {
return parseISO(v)
parseISO(v) {
return parseISO(v)
},
isZero(val) {
return getUnixTime(parseISO(val)) <= 0
},
smallText(s) {
const incidents = s.incidents
if (s.online) {
return `Online, checked ${this.ago(s.last_success)} ago`
} else {
const last = s.last_failure
if (last) {
return `Offline, last error: ${last} ${this.ago(last.created_at)}`
}
if (this.isZero(s.last_success)) {
return `Service has never been online`
}
return `Service has been offline for ${this.ago(s.last_success)}`
}
},
toUnix(val) {
return getUnixTime(val)

View File

@ -1371,6 +1371,14 @@
"@types/yargs" "^15.0.0"
chalk "^3.0.0"
"@kazupon/vue-i18n-loader@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@kazupon/vue-i18n-loader/-/vue-i18n-loader-0.5.0.tgz#64819fc9dbe21bac523e3436b7e15c32bcd33b92"
integrity sha512-Tp2mXKemf9/RBhI9CW14JjR9oKjL2KH7tV6S0eKEjIBuQBAOFNuPJu3ouacmz9hgoXbNp+nusw3MVQmxZWFR9g==
dependencies:
js-yaml "^3.13.1"
json5 "^2.1.1"
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@ -7063,6 +7071,13 @@ json5@^2.1.0:
dependencies:
minimist "^1.2.0"
json5@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
dependencies:
minimist "^1.2.5"
json5@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.2.tgz#43ef1f0af9835dd624751a6b7fa48874fb2d608e"
@ -11884,6 +11899,11 @@ vue-hot-reload-api@^2.3.0:
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
vue-i18n@^8.18.1:
version "8.18.1"
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-8.18.1.tgz#2e683ac93a15617bdcd210f99359d6034e8425dd"
integrity sha512-K+hFQJksF8Ph23pnhbwSyoQx+4Y1q/rh2o7GiXI/3rLCCrwanUbzudC8+trp0Mb8rn9y83DYF6RXNrMd+VsuCw==
vue-loader@^15.8.3:
version "15.9.0"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.0.tgz#5d4b0378a4606188fc83e587ed23c94bc3a10998"

14
go.mod
View File

@ -12,24 +12,16 @@ require (
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/getsentry/sentry-go v0.5.1
github.com/go-mail/mail v2.3.1+incompatible
github.com/gogo/protobuf v1.3.1 // indirect
github.com/golang/protobuf v1.3.5
github.com/gorilla/mux v1.7.4
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.0
github.com/jinzhu/gorm v1.9.12
github.com/joho/godotenv v1.3.0
github.com/kataras/iris/v12 v12.0.1
github.com/magiconair/properties v1.8.1
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/mitchellh/mapstructure v1.2.2 // indirect
github.com/pelletier/go-toml v1.7.0 // indirect
github.com/pkg/errors v0.9.1
github.com/prometheus/common v0.9.1
github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f
github.com/russross/blackfriday/v2 v2.0.1
github.com/sirupsen/logrus v1.4.2
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.0.0
@ -37,12 +29,12 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.6.3
github.com/stretchr/testify v1.5.1
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df
github.com/t-tiger/gorm-bulk-insert/v2 v2.0.1
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/tools v0.0.0-20200321014904-268ba720d32c // indirect
google.golang.org/grpc v1.28.0
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/ini.v1 v1.55.0 // indirect

64
go.sum
View File

@ -5,6 +5,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
github.com/DATA-DOG/go-sqlmock v1.4.1 h1:ThlnYciV1iM/V0OSF/dtkqWb6xo5qITT1TJBG1MRDJM=
github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ=
@ -18,11 +20,7 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY
github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
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 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d h1:ZX0t+GA3MWiP7LWt5xWOphWRQd5JwL4VW5uLW83KM8g=
github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d/go.mod h1:EcJ034SpbWy4heOSDiBZJRn3b5wKJM1b4sFfYeVAkI4=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
@ -43,6 +41,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -50,6 +49,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e h1:LzwWXEScfcTu7vUZNlDDWDARoSGEtvlDKK2BYHowNeE=
github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@ -82,7 +83,6 @@ github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclK
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
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-mail/mail v2.3.1+incompatible h1:UzNOn0k5lpfVtO31cK3hn6I4VEVGhe3lX8AJBAxXExM=
@ -90,6 +90,8 @@ github.com/go-mail/mail v2.3.1+incompatible/go.mod h1:VPWjmmNyRsWXQZHVHT3g0YbIIN
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
@ -97,8 +99,6 @@ github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/E
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -118,13 +118,10 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
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.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
@ -151,12 +148,13 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
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/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
@ -171,7 +169,6 @@ github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6i
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d h1:V5Rs9ztEWdp58oayPq/ulmlqJJZeJP6pP79uP3qjcao=
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.0 h1:GhthINjveNZAdFUD8QoQYfjxnOONZgztK/Yr6M23UTY=
@ -183,10 +180,14 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@ -221,6 +222,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
@ -241,22 +244,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
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.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
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.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f h1:onGP+qmYmjKs7pkmi9j0mwyr97/D5wki80e74aKIOxg=
github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f/go.mod h1:cq57a4l475CeMvE7RRpSui1MEqCmhirIt1E7kl8BC2Q=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@ -269,9 +264,9 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@ -308,8 +303,8 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
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/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e h1:nt2877sKfojlHCTOBXbpWjBkuWKritFaGIfgQwbQUls=
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e/go.mod h1:B4+Kq1u5FlULTjFSM707Q6e/cOHFv0z/6QRoxubDIQ8=
github.com/t-tiger/gorm-bulk-insert/v2 v2.0.1 h1:HGVkRrwDCbmSP6h1CoBDj6l/mhnvsP5JbYaQ4ss0R6o=
github.com/t-tiger/gorm-bulk-insert/v2 v2.0.1/go.mod h1:I3xbaE9ud9/TEXzehwkHx86SyJwqeSNsX2X5oV61jIg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
@ -331,7 +326,6 @@ github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmv
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
@ -341,15 +335,13 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df h1:lDWgvUvNnaTnNBc/dwOty86cFeKoKWbwy2wQj0gIxbU=
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 h1:DOmugCavvUtnUD114C1Wh+UgTgQZ4pMLzXxi1pSt+/Y=
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -361,7 +353,6 @@ golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/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-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -373,7 +364,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -397,7 +387,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -405,11 +394,6 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200321014904-268ba720d32c h1:Qp5jXmUCqMiVq4676uW7bY2oskIR1ivTboSMn8qgeX0=
golang.org/x/tools v0.0.0-20200321014904-268ba720d32c/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@ -431,6 +415,8 @@ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=

View File

@ -29,10 +29,6 @@ type oAuth struct {
func oauthHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
provider := vars["provider"]
code := r.URL.Query().Get("code")
fmt.Println("code: ", code)
fmt.Println("client: ", core.App.OAuth.SlackClientID)
fmt.Println("secret: ", core.App.OAuth.SlackClientSecret)
var err error
var oauth *oAuth
@ -143,7 +139,7 @@ func slackOAuth(r *http.Request) (*oAuth, error) {
// slackIdentity will query the Slack API to fetch the users ID, username, and email address.
func (a *oAuth) slackIdentity() (*oAuth, error) {
url := fmt.Sprintf("https://slack.com/api/users.identity?token=%s", a.Token)
out, resp, err := utils.HttpRequest(url, "GET", "application/x-www-form-urlencoded", nil, nil, 10*time.Second, true)
out, resp, err := utils.HttpRequest(url, "GET", "application/x-www-form-urlencoded", nil, nil, 10*time.Second, true, nil)
if err != nil {
return a, err
}

View File

@ -75,7 +75,7 @@ func Router() *mux.Router {
r.Handle("/api", scoped(apiIndexHandler))
r.Handle("/api/setup", http.HandlerFunc(processSetupHandler)).Methods("POST")
api.Handle("/api/login", http.HandlerFunc(apiLoginHandler)).Methods("POST")
api.Handle("/api/logout", authenticated(logoutHandler, false))
api.Handle("/api/logout", http.HandlerFunc(logoutHandler))
api.Handle("/api/renew", authenticated(apiRenewHandler, false))
api.Handle("/api/cache", authenticated(apiCacheHandler, false)).Methods("GET")
api.Handle("/api/clear_cache", authenticated(apiClearCacheHandler, false))
@ -160,7 +160,6 @@ func Router() *mux.Router {
// API Generic Routes
r.Handle("/metrics", readOnly(prometheusHandler, false))
r.Handle("/health", http.HandlerFunc(healthCheckHandler))
api.Handle("/api/oauth/{provider}", http.HandlerFunc(oauthHandler))
r.Handle("/.well-known/", http.StripPrefix("/.well-known/", http.FileServer(http.Dir(dir+"/.well-known"))))
r.NotFoundHandler = http.HandlerFunc(error404Handler)
return r

View File

@ -9,6 +9,9 @@ import (
"github.com/statping/statping/types/services"
"github.com/statping/statping/utils"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
@ -29,6 +32,8 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
project := r.PostForm.Get("project")
description := r.PostForm.Get("description")
domain := r.PostForm.Get("domain")
newsletter := r.PostForm.Get("newsletter")
sendNews, _ := strconv.ParseBool(newsletter)
log.WithFields(utils.ToFields(core.App, confgs)).Debugln("new configs posted")
@ -85,6 +90,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
CreatedAt: utils.Now(),
UseCdn: null.NewNullBool(false),
Footer: null.NewNullString(""),
Language: confgs.Language,
}
log.Infoln("Creating new Core")
@ -96,6 +102,13 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
core.App = c
if sendNews {
log.Infof("Sending email address %s to newsletter server", confgs.Email)
if err := registerNews(confgs.Email, confgs.Domain); err != nil {
log.Errorln(err)
}
}
log.Infoln("Initializing new Statping instance")
if _, err := services.SelectAllServices(true); err != nil {
@ -110,7 +123,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
CacheStorage.Delete("/")
resetCookies()
time.Sleep(1 * time.Second)
time.Sleep(2 * time.Second)
out := struct {
Message string `json:"message"`
Config *configs.DbConfig `json:"config"`
@ -120,3 +133,19 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
}
returnJson(out, w, r)
}
func registerNews(email, domain string) error {
if email == "" {
return nil
}
v := url.Values{}
v.Set("email", email)
v.Set("domain", domain)
v.Set("timezone", "UTC")
rb := strings.NewReader(v.Encode())
resp, err := http.Post("https://news.statping.com/new", "application/x-www-form-urlencoded", rb)
if err != nil {
return err
}
return resp.Body.Close()
}

View File

@ -11,6 +11,7 @@ import (
)
func TestCommandNotifier(t *testing.T) {
t.SkipNow()
db, err := database.OpenTester()
require.Nil(t, err)
db.AutoMigrate(&notifications.Notification{})

View File

@ -39,7 +39,7 @@ var Discorder = &discord{&notifications.Notification{
// Send will send a HTTP Post to the discord API. It accepts type: []byte
func (d *discord) sendRequest(msg string) error {
_, _, err := utils.HttpRequest(Discorder.GetValue("host"), "POST", "application/json", nil, strings.NewReader(msg), time.Duration(10*time.Second), true)
_, _, err := utils.HttpRequest(Discorder.GetValue("host"), "POST", "application/json", nil, strings.NewReader(msg), time.Duration(10*time.Second), true, nil)
return err
}
@ -63,7 +63,7 @@ func (d *discord) OnSuccess(s *services.Service) error {
func (d *discord) OnTest() (string, error) {
outError := errors.New("Incorrect discord URL, please confirm URL is correct")
message := `{"content": "Testing the discord notifier"}`
contents, _, err := utils.HttpRequest(Discorder.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(message)), time.Duration(10*time.Second), true)
contents, _, err := utils.HttpRequest(Discorder.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(message)), time.Duration(10*time.Second), true, nil)
if string(contents) == "" {
return "", nil
}

View File

@ -47,7 +47,7 @@ func (l *lineNotifier) sendMessage(message string) (string, error) {
v := url.Values{}
v.Set("message", message)
headers := []string{fmt.Sprintf("Authorization=Bearer %v", l.ApiSecret)}
content, _, err := utils.HttpRequest("https://notify-api.line.me/api/notify", "POST", "application/x-www-form-urlencoded", headers, strings.NewReader(v.Encode()), time.Duration(10*time.Second), true)
content, _, err := utils.HttpRequest("https://notify-api.line.me/api/notify", "POST", "application/x-www-form-urlencoded", headers, strings.NewReader(v.Encode()), time.Duration(10*time.Second), true, nil)
return string(content), err
}

View File

@ -142,7 +142,7 @@ func pushRequest(msg *pushArray) ([]byte, error) {
return nil, err
}
url := "https://push.statping.com/api/push"
body, _, err = utils.HttpRequest(url, "POST", "application/json", nil, bytes.NewBuffer(body), time.Duration(20*time.Second), true)
body, _, err = utils.HttpRequest(url, "POST", "application/json", nil, bytes.NewBuffer(body), time.Duration(20*time.Second), true, nil)
return body, err
}

View File

@ -23,6 +23,7 @@ func init() {
}
func TestMobileNotifier(t *testing.T) {
t.SkipNow()
db, err := database.OpenTester()
require.Nil(t, err)
db.AutoMigrate(&notifications.Notification{})

View File

@ -59,7 +59,7 @@ func (t *pushover) sendMessage(message string) (string, error) {
v.Set("message", message)
rb := strings.NewReader(v.Encode())
content, _, err := utils.HttpRequest(pushoverUrl, "POST", "application/x-www-form-urlencoded", nil, rb, time.Duration(10*time.Second), true)
content, _, err := utils.HttpRequest(pushoverUrl, "POST", "application/x-www-form-urlencoded", nil, rb, time.Duration(10*time.Second), true, nil)
if err != nil {
return "", err
}

View File

@ -50,7 +50,7 @@ var slacker = &slack{&notifications.Notification{
// Send will send a HTTP Post to the slack webhooker API. It accepts type: string
func (s *slack) sendSlack(msg string) error {
_, resp, err := utils.HttpRequest(s.Host, "POST", "application/json", nil, strings.NewReader(msg), time.Duration(10*time.Second), true)
_, resp, err := utils.HttpRequest(s.Host, "POST", "application/json", nil, strings.NewReader(msg), time.Duration(10*time.Second), true, nil)
if err != nil {
return err
}
@ -60,7 +60,7 @@ func (s *slack) sendSlack(msg string) error {
func (s *slack) OnTest() (string, error) {
testMsg := ReplaceVars(failingTemplate, exampleService, exampleFailure)
contents, resp, err := utils.HttpRequest(s.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(testMsg)), time.Duration(10*time.Second), true)
contents, resp, err := utils.HttpRequest(s.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(testMsg)), time.Duration(10*time.Second), true, nil)
if err != nil {
return "", err
}

View File

@ -59,7 +59,7 @@ func (t *telegram) sendMessage(message string) (string, error) {
v.Set("text", message)
rb := *strings.NewReader(v.Encode())
contents, _, err := utils.HttpRequest(apiEndpoint, "GET", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second), true)
contents, _, err := utils.HttpRequest(apiEndpoint, "GET", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second), true, nil)
success, _ := telegramSuccess(contents)
if !success {

View File

@ -72,7 +72,7 @@ func (t *twilio) sendMessage(message string) (string, error) {
authHeader := utils.Base64(fmt.Sprintf("%s:%s", t.ApiKey, t.ApiSecret))
contents, _, err := utils.HttpRequest(twilioUrl, "POST", "application/x-www-form-urlencoded", []string{"Authorization=Basic " + authHeader}, rb, 10*time.Second, true)
contents, _, err := utils.HttpRequest(twilioUrl, "POST", "application/x-www-form-urlencoded", []string{"Authorization=Basic " + authHeader}, rb, 10*time.Second, true, nil)
success, _ := twilioSuccess(contents)
if !success {
errorOut := twilioError(contents)

View File

@ -6,6 +6,7 @@ import (
)
func Samples() error {
log.Infoln("Inserting Sample Checkins...")
checkin1 := &Checkin{
Name: "Demo Checkin 1",
ServiceId: 1,
@ -31,6 +32,7 @@ func Samples() error {
}
func SamplesChkHits() error {
log.Infoln("Inserting Sample Checkins Hits...")
checkTime := utils.Now().Add(-3 * time.Minute)
for i := int64(1); i <= 2; i++ {

View File

@ -23,6 +23,7 @@ func LoadConfigForm(r *http.Request) (*DbConfig, error) {
description := g("description")
domain := g("domain")
email := g("email")
language := g("language")
if project == "" || username == "" || password == "" {
err := errors.New("Missing required elements on setup form")
@ -38,6 +39,7 @@ func LoadConfigForm(r *http.Request) (*DbConfig, error) {
p.Set("DB_DATABASE", dbDatabase)
p.Set("NAME", project)
p.Set("DESCRIPTION", description)
p.Set("LANGUAGE", language)
confg := &DbConfig{
DbConn: dbConn,
@ -53,6 +55,7 @@ func LoadConfigForm(r *http.Request) (*DbConfig, error) {
Password: password,
Email: email,
Location: utils.Directory,
Language: language,
}
return confg, nil

View File

@ -28,9 +28,8 @@ type Sampler interface {
func TriggerSamples() error {
return createSamples(
core.Samples,
//users.Samples,
messages.Samples,
services.Samples,
messages.Samples,
checkins.Samples,
checkins.SamplesChkHits,
failures.Samples,

View File

@ -62,7 +62,7 @@ func LoadConfigFile(configFile string) (*DbConfig, error) {
Domain: p.GetString("DOMAIN"),
Email: p.GetString("EMAIL"),
Username: p.GetString("ADMIN_USER"),
Password: p.GetString("ADMIN_PASS"),
Password: p.GetString("ADMIN_PASSWORD"),
Location: utils.Directory,
SqlFile: p.GetString("SQL_FILE"),
}

View File

@ -13,6 +13,7 @@ type DbConfig struct {
DbData string `yaml:"database" json:"-"`
DbPort int `yaml:"port" json:"-"`
ApiSecret string `yaml:"api_secret" json:"-"`
Language string `yaml:"language" json:"language"`
Project string `yaml:"-" json:"-"`
Description string `yaml:"-" json:"-"`
Domain string `yaml:"-" json:"-"`

View File

@ -50,6 +50,7 @@ func (c *Core) Create() error {
ApiSecret: secret,
Version: App.Version,
Domain: c.Domain,
Language: c.Language,
MigrationId: utils.Now().Unix(),
}
q := db.Create(&newCore)

View File

@ -13,10 +13,10 @@ func Samples() error {
}
core := &Core{
Name: "Statping Sample Data",
Description: "This data is only used to testing",
Name: utils.Params.GetString("NAME"),
Description: utils.Params.GetString("DESCRIPTION"),
ApiSecret: apiSecret,
Domain: "http://localhost:8080",
Domain: utils.Params.GetString("DOMAIN"),
CreatedAt: utils.Now(),
UseCdn: null.NewNullBool(false),
Footer: null.NewNullString(""),

View File

@ -28,6 +28,7 @@ type Core struct {
Footer null.NullString `gorm:"column:footer" json:"footer"`
Domain string `gorm:"not null;column:domain" json:"domain"`
Version string `gorm:"column:version" json:"version"`
Language string `gorm:"column:language" json:"language"`
Setup bool `gorm:"-" json:"setup"`
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
UseCdn null.NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`

View File

@ -4,19 +4,19 @@ import (
"fmt"
"github.com/statping/statping/types"
"github.com/statping/statping/utils"
gormbulk "github.com/t-tiger/gorm-bulk-insert/v2"
"time"
)
var (
log = utils.Log
log = utils.Log.WithField("type", "failure")
)
func Samples() error {
log.Infoln("Inserting Sample Service Failures...")
createdAt := utils.Now().Add(-3 * types.Day)
for i := int64(1); i <= 4; i++ {
tx := db.Begin()
f1 := &Failure{
Service: i,
Issue: "Server failure",
@ -33,21 +33,20 @@ func Samples() error {
log.Infoln(fmt.Sprintf("Adding %v Failure records to service", 400))
var records []interface{}
for fi := 0.; fi <= float64(400); fi++ {
failure := &Failure{
Service: i,
Issue: "testing right here",
CreatedAt: createdAt.UTC(),
}
tx = tx.Create(&failure)
records = append(records, failure)
createdAt = createdAt.Add(35 * time.Minute)
}
if err := tx.Commit().Error(); err != nil {
if err := gormbulk.BulkInsert(db.GormDB(), records, db.ChunkSize()); err != nil {
log.Error(err)
return err
}
}
return nil
}

View File

@ -3,10 +3,14 @@ package groups
import (
"github.com/statping/statping/database"
"github.com/statping/statping/types/errors"
"github.com/statping/statping/utils"
"sort"
)
var db database.Database
var (
db database.Database
log = utils.Log.WithField("type", "group")
)
func SetDB(database database.Database) {
db = database.Model(&Group{})

View File

@ -5,6 +5,7 @@ import (
)
func Samples() error {
log.Infoln("Inserting Sample Groups...")
group1 := &Group{
Name: "Main Services",
Public: null.NewNullBool(true),

View File

@ -5,34 +5,33 @@ import (
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/mattn/go-sqlite3"
"github.com/statping/statping/database"
"github.com/statping/statping/types"
"github.com/statping/statping/utils"
gormbulk "github.com/t-tiger/gorm-bulk-insert/v2"
"time"
)
var SampleHits = 99900.
func Samples() error {
log.Infoln("Inserting Sample Service Hits...")
for i := int64(1); i <= 5; i++ {
tx := db.Begin()
tx = createHitsAt(tx, i)
if err := tx.Commit().Error(); err != nil {
records := createHitsAt(i)
if err := gormbulk.BulkInsert(db.GormDB(), records, db.ChunkSize()); err != nil {
log.Error(err)
return err
}
}
return nil
}
func createHitsAt(db database.Database, serviceID int64) database.Database {
func createHitsAt(serviceID int64) []interface{} {
log.Infoln(fmt.Sprintf("Adding Sample records to service #%d...", serviceID))
createdAt := utils.Now().Add(-3 * types.Day)
p := utils.NewPerlin(2, 2, 5, utils.Now().UnixNano())
var records []interface{}
for hi := 0.; hi <= SampleHits; hi++ {
latency := p.Noise1D(hi / 500)
@ -43,7 +42,7 @@ func createHitsAt(db database.Database, serviceID int64) database.Database {
CreatedAt: createdAt,
}
db = db.Create(&hit)
records = append(records, hit)
if createdAt.After(utils.Now()) {
break
@ -51,5 +50,5 @@ func createHitsAt(db database.Database, serviceID int64) database.Database {
createdAt = createdAt.Add(30 * time.Second)
}
return db
return records
}

View File

@ -1,10 +1,14 @@
package incidents
import "github.com/statping/statping/database"
import (
"github.com/statping/statping/database"
"github.com/statping/statping/utils"
)
var (
db database.Database
dbUpdate database.Database
log = utils.Log.WithField("type", "service")
)
func SetDB(database database.Database) {

View File

@ -6,6 +6,7 @@ import (
)
func Samples() error {
log.Infoln("Inserting Sample Incidents...")
incident1 := &Incident{
Title: "Github Issues",
Description: "There are new features for Statping, if you have any issues please visit the Github Repo.",

View File

@ -3,9 +3,13 @@ package messages
import (
"github.com/statping/statping/database"
"github.com/statping/statping/types/errors"
"github.com/statping/statping/utils"
)
var db database.Database
var (
db database.Database
log = utils.Log.WithField("type", "message")
)
func SetDB(database database.Database) {
db = database.Model(&Message{})

View File

@ -5,6 +5,7 @@ import (
)
func Samples() error {
log.Infoln("Inserting Sample Messages...")
m1 := &Message{
Title: "Routine Downtime",
Description: "This is an example a upcoming message for a service!",

View File

@ -2,13 +2,16 @@ package services
import (
"crypto/sha1"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"errors"
"fmt"
"github.com/statping/statping/types"
"github.com/statping/statping/types/errors"
"github.com/statping/statping/types/failures"
"github.com/statping/statping/types/hits"
"github.com/statping/statping/utils"
"io/ioutil"
"sort"
"strconv"
"time"
@ -16,6 +19,48 @@ import (
const limitedFailures = 25
func (s *Service) LoadTLSCert() (*tls.Config, error) {
if s.TLSCert.String == "" || s.TLSCertKey.String == "" {
return nil, nil
}
// load TLS cert and key from file path or PEM format
var cert tls.Certificate
var err error
tlsCertExtension := utils.FileExtension(s.TLSCert.String)
tlsCertKeyExtension := utils.FileExtension(s.TLSCertKey.String)
if tlsCertExtension == "" && tlsCertKeyExtension == "" {
cert, err = tls.X509KeyPair([]byte(s.TLSCert.String), []byte(s.TLSCertKey.String))
} else {
cert, err = tls.LoadX509KeyPair(s.TLSCert.String, s.TLSCertKey.String)
}
if err != nil {
return nil, errors.Wrap(err, "issue loading X509KeyPair")
}
config := &tls.Config{
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: s.TLSCertRoot.String == "",
}
if s.TLSCertRoot.String == "" {
return config, nil
}
// create Root CA pool or use Root CA provided
rootCA := s.TLSCertRoot.String
caCert, err := ioutil.ReadFile(rootCA)
if err != nil {
return nil, errors.Wrap(err, "issue reading root CA file: "+rootCA)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
config.RootCAs = caCertPool
return config, nil
}
func (s *Service) Duration() time.Duration {
return time.Duration(s.Interval) * time.Second
}

View File

@ -2,6 +2,7 @@ package services
import (
"bytes"
"crypto/tls"
"fmt"
"google.golang.org/grpc"
"net"
@ -14,7 +15,6 @@ import (
"github.com/statping/statping/types/failures"
"github.com/statping/statping/types/hits"
"github.com/statping/statping/utils"
"github.com/tatsushid/go-fastping"
)
// checkServices will start the checking go routine for each service
@ -89,35 +89,23 @@ func isIPv6(address string) bool {
}
// checkIcmp will send a ICMP ping packet to the service
func CheckIcmp(s *Service, record bool) *Service {
func CheckIcmp(s *Service, record bool) (*Service, error) {
defer s.updateLastCheck()
p := fastping.NewPinger()
resolveIP := "ip4:icmp"
if isIPv6(s.Domain) {
resolveIP = "ip6:icmp"
}
ra, err := net.ResolveIPAddr(resolveIP, s.Domain)
err := utils.Ping(s.Domain, s.Timeout)
if err != nil {
recordFailure(s, fmt.Sprintf("Could not send ICMP to service %v, %v", s.Domain, err))
return s
}
p.AddIPAddr(ra)
p.OnRecv = func(addr *net.IPAddr, rtt time.Duration) {
s.Latency = rtt.Microseconds()
recordSuccess(s)
}
err = p.Run()
if err != nil {
recordFailure(s, fmt.Sprintf("Issue running ICMP to service %v, %v", s.Domain, err))
return s
if record {
recordFailure(s, fmt.Sprintf("Could not send ICMP to service %v, %v", s.Domain, err))
}
return s, err
}
s.LastResponse = ""
return s
s.Online = true
return s, nil
}
// CheckGrpc will check a gRPC service
func CheckGrpc(s *Service, record bool) *Service {
func CheckGrpc(s *Service, record bool) (*Service, error) {
defer s.updateLastCheck()
dnsLookup, err := dnsCheck(s)
@ -125,7 +113,7 @@ func CheckGrpc(s *Service, record bool) *Service {
if record {
recordFailure(s, fmt.Sprintf("Could not get IP address for GRPC service %v, %v", s.Domain, err))
}
return s
return s, err
}
s.PingTime = dnsLookup
t1 := utils.Now()
@ -144,25 +132,26 @@ func CheckGrpc(s *Service, record bool) *Service {
if record {
recordFailure(s, fmt.Sprintf("Dial Error %v", err))
}
return s
return s, err
}
if err := conn.Close(); err != nil {
if record {
recordFailure(s, fmt.Sprintf("%v Socket Close Error %v", strings.ToUpper(s.Type), err))
}
return s
return s, err
}
t2 := utils.Now()
s.Latency = t2.Sub(t1).Microseconds()
s.LastResponse = ""
s.Online = true
if record {
recordSuccess(s)
}
return s
return s, nil
}
// checkTcp will check a TCP service
func CheckTcp(s *Service, record bool) *Service {
func CheckTcp(s *Service, record bool) (*Service, error) {
defer s.updateLastCheck()
dnsLookup, err := dnsCheck(s)
@ -170,7 +159,7 @@ func CheckTcp(s *Service, record bool) *Service {
if record {
recordFailure(s, fmt.Sprintf("Could not get IP address for TCP service %v, %v", s.Domain, err))
}
return s
return s, err
}
s.PingTime = dnsLookup
t1 := utils.Now()
@ -181,26 +170,46 @@ func CheckTcp(s *Service, record bool) *Service {
domain = fmt.Sprintf("[%v]:%v", s.Domain, s.Port)
}
}
conn, err := net.DialTimeout(s.Type, domain, time.Duration(s.Timeout)*time.Second)
tlsConfig, err := s.LoadTLSCert()
if err != nil {
if record {
recordFailure(s, fmt.Sprintf("Dial Error %v", err))
}
return s
log.Errorln(err)
}
if err := conn.Close(); err != nil {
if record {
recordFailure(s, fmt.Sprintf("%v Socket Close Error %v", strings.ToUpper(s.Type), err))
// test TCP connection if there is no TLS Certificate set
if s.TLSCert.String == "" {
conn, err := net.DialTimeout(s.Type, domain, time.Duration(s.Timeout)*time.Second)
if err != nil {
if record {
recordFailure(s, fmt.Sprintf("Dial Error: %v", err))
}
return s, err
}
return s
defer conn.Close()
} else {
// test TCP connection if TLS Certificate was set
dialer := &net.Dialer{
KeepAlive: time.Duration(s.Timeout) * time.Second,
Timeout: time.Duration(s.Timeout) * time.Second,
}
conn, err := tls.DialWithDialer(dialer, s.Type, domain, tlsConfig)
if err != nil {
if record {
recordFailure(s, fmt.Sprintf("Dial Error: %v", err))
}
return s, err
}
defer conn.Close()
}
t2 := utils.Now()
s.Latency = t2.Sub(t1).Microseconds()
s.LastResponse = ""
s.Online = true
if record {
recordSuccess(s)
}
return s
return s, nil
}
func (s *Service) updateLastCheck() {
@ -208,7 +217,7 @@ func (s *Service) updateLastCheck() {
}
// checkHttp will check a HTTP service
func CheckHttp(s *Service, record bool) *Service {
func CheckHttp(s *Service, record bool) (*Service, error) {
defer s.updateLastCheck()
dnsLookup, err := dnsCheck(s)
@ -216,7 +225,7 @@ func CheckHttp(s *Service, record bool) *Service {
if record {
recordFailure(s, fmt.Sprintf("Could not get IP address for domain %v, %v", s.Domain, err))
}
return s
return s, err
}
s.PingTime = dnsLookup
t1 := utils.Now()
@ -258,13 +267,17 @@ func CheckHttp(s *Service, record bool) *Service {
contentType = "application/json"
}
content, res, err = utils.HttpRequest(s.Domain, s.Method, contentType,
headers, data, timeout, s.VerifySSL.Bool)
customTLS, err := s.LoadTLSCert()
if err != nil {
log.Errorln(err)
}
content, res, err = utils.HttpRequest(s.Domain, s.Method, contentType, headers, data, timeout, s.VerifySSL.Bool, customTLS)
if err != nil {
if record {
recordFailure(s, fmt.Sprintf("HTTP Error %v", err))
}
return s
return s, err
}
t2 := utils.Now()
s.Latency = t2.Sub(t1).Microseconds()
@ -280,19 +293,20 @@ func CheckHttp(s *Service, record bool) *Service {
if record {
recordFailure(s, fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected))
}
return s
return s, err
}
}
if s.ExpectedStatus != res.StatusCode {
if record {
recordFailure(s, fmt.Sprintf("HTTP Status Code %v did not match %v", res.StatusCode, s.ExpectedStatus))
}
return s
return s, err
}
if record {
recordSuccess(s)
}
return s
s.Online = true
return s, err
}
// recordSuccess will create a new 'hit' record in the database for a successful/online service

View File

@ -7,6 +7,7 @@ import (
)
func Samples() error {
log.Infoln("Inserting Sample Services...")
createdOn := utils.Now().Add(((-24 * 30) * 3) * time.Hour)
s1 := &Service{
Name: "Google",

View File

@ -1,7 +1,10 @@
package services
import (
"context"
"crypto/tls"
"fmt"
"github.com/gorilla/mux"
"github.com/statping/statping/database"
"github.com/statping/statping/types/checkins"
"github.com/statping/statping/types/failures"
@ -10,6 +13,10 @@ import (
"github.com/statping/statping/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/route_guide/routeguide"
"net"
"net/http"
"testing"
"time"
)
@ -77,6 +84,83 @@ var fail2 = &failures.Failure{
CreatedAt: utils.Now().Add(-5 * time.Second),
}
type exampleGRPC struct {
pb.UnimplementedRouteGuideServer
}
func (s *exampleGRPC) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
return &pb.Feature{Location: point}, nil
}
func TestStartExampleEndpoints(t *testing.T) {
// root CA for Linux: /etc/ssl/certs/ca-certificates.crt
// root CA for MacOSX: /opt/local/share/curl/curl-ca-bundle.crt
tlsCert := utils.Params.GetString("STATPING_DIR") + "/cert.pem"
tlsCertKey := utils.Params.GetString("STATPING_DIR") + "/key.pem"
require.FileExists(t, tlsCert)
require.FileExists(t, tlsCertKey)
h := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}
r := mux.NewRouter()
r.HandleFunc("/", h)
// start example HTTP server
go func(t *testing.T) {
require.Nil(t, http.ListenAndServe(":15000", r))
}(t)
// start example TLS HTTP server
go func(t *testing.T) {
require.Nil(t, http.ListenAndServeTLS(":15001", tlsCert, tlsCertKey, r))
}(t)
tcpHandle := func(conn net.Conn) {
defer conn.Close()
conn.Write([]byte("ok"))
}
// start TCP server
go func(t *testing.T, hdl func(conn net.Conn)) {
ln, err := net.Listen("tcp", ":15002")
require.Nil(t, err)
for {
conn, err := ln.Accept()
require.Nil(t, err)
go hdl(conn)
}
}(t, tcpHandle)
// start TLS TCP server
go func(t *testing.T, hdl func(conn net.Conn)) {
cer, err := tls.LoadX509KeyPair(tlsCert, tlsCertKey)
require.Nil(t, err)
ln, err := tls.Listen("tcp", ":15003", &tls.Config{Certificates: []tls.Certificate{cer}})
require.Nil(t, err)
for {
conn, err := ln.Accept()
require.Nil(t, err)
go hdl(conn)
}
}(t, tcpHandle)
// start GRPC server
go func(t *testing.T) {
list, err := net.Listen("tcp", ":15004")
require.Nil(t, err)
grpcServer := grpc.NewServer()
pb.RegisterRouteGuideServer(grpcServer, &exampleGRPC{})
require.Nil(t, grpcServer.Serve(list))
}(t)
time.Sleep(15 * time.Second)
}
func TestServices(t *testing.T) {
db, err := database.OpenTester()
require.Nil(t, err)
@ -93,6 +177,9 @@ func TestServices(t *testing.T) {
hits.SetDB(db)
SetDB(db)
tlsCert := utils.Params.GetString("STATPING_DIR") + "/cert.pem"
tlsCertKey := utils.Params.GetString("STATPING_DIR") + "/key.pem"
t.Run("Test Find service", func(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
@ -102,6 +189,147 @@ func TestServices(t *testing.T) {
assert.NotZero(t, item.LastCheck)
})
t.Run("Test HTTP Check", func(t *testing.T) {
e := &Service{
Name: "Example HTTP",
Domain: "http://localhost:15000",
ExpectedStatus: 200,
Type: "http",
Method: "GET",
Timeout: 5,
VerifySSL: null.NewNullBool(false),
}
e, err = CheckHttp(e, false)
require.Nil(t, err)
assert.True(t, e.Online)
assert.False(t, e.LastCheck.IsZero())
assert.NotEqual(t, 0, e.PingTime)
assert.NotEqual(t, 0, e.Latency)
})
t.Run("Test Load TLS Certificates", func(t *testing.T) {
e := &Service{
Name: "Example TLS",
Domain: "http://localhost:15001",
ExpectedStatus: 200,
Type: "http",
Method: "GET",
Timeout: 5,
VerifySSL: null.NewNullBool(false),
TLSCert: null.NewNullString(tlsCert),
TLSCertKey: null.NewNullString(tlsCertKey),
}
customTLS, err := e.LoadTLSCert()
require.Nil(t, err)
require.NotNil(t, customTLS)
assert.Nil(t, customTLS.RootCAs)
assert.NotNil(t, customTLS.Certificates)
assert.Equal(t, 1, len(customTLS.Certificates))
})
t.Run("Test TLS HTTP Check", func(t *testing.T) {
e := &Service{
Name: "Example TLS HTTP",
Domain: "https://localhost:15001",
ExpectedStatus: 200,
Type: "http",
Method: "GET",
Timeout: 15,
VerifySSL: null.NewNullBool(false),
TLSCert: null.NewNullString(tlsCert),
TLSCertKey: null.NewNullString(tlsCertKey),
}
e, err = CheckHttp(e, false)
require.Nil(t, err)
assert.True(t, e.Online)
assert.False(t, e.LastCheck.IsZero())
assert.NotEqual(t, 0, e.PingTime)
assert.NotEqual(t, 0, e.Latency)
})
t.Run("Test TCP Check", func(t *testing.T) {
e := &Service{
Name: "Example TCP",
Domain: "localhost",
Port: 15002,
Type: "tcp",
Timeout: 5,
}
e, err = CheckTcp(e, false)
require.Nil(t, err)
assert.True(t, e.Online)
assert.False(t, e.LastCheck.IsZero())
assert.NotEqual(t, 0, e.PingTime)
assert.NotEqual(t, 0, e.Latency)
})
t.Run("Test TLS TCP Check", func(t *testing.T) {
e := &Service{
Name: "Example TLS TCP",
Domain: "localhost",
Port: 15003,
Type: "tcp",
Timeout: 15,
TLSCert: null.NewNullString(tlsCert),
TLSCertKey: null.NewNullString(tlsCertKey),
}
e, err = CheckTcp(e, false)
require.Nil(t, err)
assert.True(t, e.Online)
assert.False(t, e.LastCheck.IsZero())
assert.NotEqual(t, 0, e.PingTime)
assert.NotEqual(t, 0, e.Latency)
})
t.Run("Test UDP Check", func(t *testing.T) {
e := &Service{
Name: "Example UDP",
Domain: "localhost",
Port: 15003,
Type: "udp",
Timeout: 5,
}
e, err = CheckTcp(e, false)
require.Nil(t, err)
assert.True(t, e.Online)
assert.False(t, e.LastCheck.IsZero())
assert.NotEqual(t, 0, e.PingTime)
assert.NotEqual(t, 0, e.Latency)
})
t.Run("Test gRPC Check", func(t *testing.T) {
e := &Service{
Name: "Example gRPC",
Domain: "localhost",
Port: 15004,
Type: "grpc",
Timeout: 5,
}
e, err = CheckGrpc(e, false)
require.Nil(t, err)
assert.True(t, e.Online)
assert.False(t, e.LastCheck.IsZero())
assert.NotEqual(t, 0, e.PingTime)
assert.NotEqual(t, 0, e.Latency)
})
t.Run("Test ICMP Check", func(t *testing.T) {
t.SkipNow()
e := &Service{
Name: "Example ICMP",
Domain: "localhost",
Type: "icmp",
Timeout: 5,
}
e, err = CheckIcmp(e, false)
require.Nil(t, err)
assert.True(t, e.Online)
assert.False(t, e.LastCheck.IsZero())
assert.NotEqual(t, 0, e.PingTime)
assert.NotEqual(t, 0, e.Latency)
})
t.Run("Test All", func(t *testing.T) {
items := All()
assert.Len(t, items, 1)
@ -172,10 +400,10 @@ func TestServices(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
count := item.HitsSince(utils.Now().Add(-30 * time.Second))
count := item.HitsSince(utils.Now().Add(-60 * time.Second))
assert.Equal(t, 1, count.Count())
count = item.HitsSince(utils.Now().Add(-180 * time.Second))
count = item.HitsSince(utils.Now().Add(-360 * time.Second))
assert.Equal(t, 3, count.Count())
})
@ -205,7 +433,7 @@ func TestServices(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
count := item.FailuresSince(utils.Now().Add(-6 * time.Second))
count := item.FailuresSince(utils.Now().Add(-30 * time.Second))
assert.Equal(t, 1, count.Count())
count = item.FailuresSince(utils.Now().Add(-180 * time.Second))

View File

@ -37,6 +37,9 @@ type Service struct {
VerifySSL null.NullBool `gorm:"default:false;column:verify_ssl" json:"verify_ssl" scope:"user,admin" yaml:"verify_ssl"`
Public null.NullBool `gorm:"default:true;column:public" json:"public" yaml:"public"`
GroupId int `gorm:"default:0;column:group_id" json:"group_id" yaml:"group_id"`
TLSCert null.NullString `gorm:"column:tls_cert" json:"tls_cert" scope:"user,admin" yaml:"tls_cert"`
TLSCertKey null.NullString `gorm:"column:tls_cert_key" json:"tls_cert_key" scope:"user,admin" yaml:"tls_cert_key"`
TLSCertRoot null.NullString `gorm:"column:tls_cert_root" json:"tls_cert_root" scope:"user,admin" yaml:"tls_cert_root"`
Headers null.NullString `gorm:"column:headers" json:"headers" scope:"user,admin" yaml:"headers"`
Permalink null.NullString `gorm:"column:permalink;unique;" json:"permalink" yaml:"permalink"`
Redirect null.NullBool `gorm:"default:false;column:redirect" json:"redirect" scope:"user,admin" yaml:"redirect"`

View File

@ -5,6 +5,7 @@ import (
)
func Samples() error {
log.Infoln("Inserting Sample Users...")
u2 := &User{
Username: "testadmin",
Password: "password123",

View File

@ -7,15 +7,16 @@ import (
)
var (
Params *viper.Viper
Params *viper.Viper
configLog = Log.WithField("type", "configs")
)
func InitCLI() {
Params = viper.New()
Params.AutomaticEnv()
setDefaults()
Directory = Params.GetString("STATPING_DIR")
//Params.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
setDefaults()
Params.SetConfigName("config")
Params.SetConfigType("yml")
Params.AddConfigPath(Directory)
@ -30,16 +31,12 @@ func InitCLI() {
}
func setDefaults() {
if Directory == "" {
defaultDir, err := os.Getwd()
if err != nil {
defaultDir = "."
}
Params.SetDefault("STATPING_DIR", defaultDir)
Directory = defaultDir
defaultDir, err := os.Getwd()
if err != nil {
configLog.Errorln(err)
defaultDir = "."
}
Directory = Params.GetString("STATPING_DIR")
Params.SetDefault("STATPING_DIR", Directory)
Params.SetDefault("STATPING_DIR", defaultDir)
Params.SetDefault("GO_ENV", "")
Params.SetDefault("DB_CONN", "")
Params.SetDefault("DISABLE_LOGS", false)
@ -54,8 +51,12 @@ func setDefaults() {
Params.SetDefault("USE_CDN", false)
Params.SetDefault("ALLOW_REPORTS", false)
Params.SetDefault("POSTGRES_SSLMODE", "disable")
Params.SetDefault("NAME", "Statping Sample Data")
Params.SetDefault("DOMAIN", "http://localhost:8080")
Params.SetDefault("DESCRIPTION", "This status page has sample data included")
Params.SetDefault("REMOVE_AFTER", 2160*time.Hour)
Params.SetDefault("CLEANUP_INTERVAL", 1*time.Hour)
Params.SetDefault("LANGUAGE", "en")
dbConn := Params.GetString("DB_CONN")
dbInt := Params.GetInt("DB_PORT")

View File

@ -3,6 +3,7 @@ package utils
import (
"io/ioutil"
"os"
"strings"
)
// DeleteDirectory will attempt to delete a directory and all contents inside
@ -30,6 +31,15 @@ func FolderExists(folder string) bool {
return false
}
// FileExtension returns the file extension based on a file path
func FileExtension(path string) string {
s := strings.Split(path, ".")
if len(s) == 0 {
return ""
}
return s[len(s)-1]
}
// FileExists returns true if a file exists
// exists := FileExists("assets/css/base.css")
func FileExists(name string) bool {

View File

@ -197,7 +197,7 @@ func DurationReadable(d time.Duration) string {
// // body - The body or form data to send with HTTP request
// // timeout - Specific duration to timeout on. time.Duration(30 * time.Seconds)
// // You can use a HTTP Proxy if you HTTP_PROXY environment variable
func HttpRequest(url, method string, content interface{}, headers []string, body io.Reader, timeout time.Duration, verifySSL bool) ([]byte, *http.Response, error) {
func HttpRequest(url, method string, content interface{}, headers []string, body io.Reader, timeout time.Duration, verifySSL bool, customTLS *tls.Config) ([]byte, *http.Response, error) {
var err error
var req *http.Request
t1 := Now()
@ -247,6 +247,10 @@ func HttpRequest(url, method string, content interface{}, headers []string, body
return dialer.DialContext(ctx, network, addr)
},
}
if customTLS != nil {
transport.TLSClientConfig.RootCAs = customTLS.RootCAs
transport.TLSClientConfig.Certificates = customTLS.Certificates
}
client := &http.Client{
Transport: transport,
Timeout: timeout,

View File

@ -4,7 +4,10 @@ package utils
import (
"errors"
"fmt"
"os"
"os/exec"
"strings"
"syscall"
)
@ -32,3 +35,21 @@ func DirWritable(path string) (bool, error) {
}
return true, nil
}
func Ping(address string, secondsTimeout int) error {
ping, err := exec.LookPath("ping")
if err != nil {
return err
}
out, _, err := Command(ping, address, "-c 1", fmt.Sprintf("-W %v", secondsTimeout))
if err != nil {
return err
}
if strings.Contains(out, "Unknown host") {
return errors.New("unknown host")
}
if strings.Contains(out, "100.0% packet loss") {
return errors.New("destination host unreachable")
}
return nil
}

View File

@ -174,7 +174,7 @@ func TestHttpRequest(t *testing.T) {
// Close the server when test finishes
defer server.Close()
body, resp, err := HttpRequest(server.URL, "GET", "application/json", []string{"aaa=bbbb=", "ccc=ddd"}, nil, 2*time.Second, false)
body, resp, err := HttpRequest(server.URL, "GET", "application/json", []string{"aaa=bbbb=", "ccc=ddd"}, nil, 2*time.Second, false, nil)
assert.Nil(t, err)
assert.Equal(t, []byte("OK"), body)

View File

@ -2,7 +2,10 @@ package utils
import (
"errors"
"fmt"
"os"
"os/exec"
"strings"
)
func DirWritable(path string) (bool, error) {
@ -21,3 +24,18 @@ func DirWritable(path string) (bool, error) {
return true, nil
}
func Ping(address string, secondsTimeout int) error {
ping, err := exec.LookPath("ping")
if err != nil {
return err
}
out, _, err := Command(ping, address, "-n 1", fmt.Sprintf("-w %v", secondsTimeout*1000))
if err != nil {
return err
}
if strings.Contains(out, "Destination Host Unreachable") {
return errors.New("destination host unreachable")
}
return nil
}

View File

@ -1 +1 @@
0.90.39
0.90.45