mirror of https://github.com/statping/statping
commit
243b6f019f
|
@ -42,7 +42,9 @@ jobs:
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ env.VERSION }}
|
VERSION: ${{ env.VERSION }}
|
||||||
COMMIT: ${{ github.sha }}
|
COMMIT: ${{ github.sha }}
|
||||||
run: make clean compile
|
MJML_APP: ${{ secrets.MJML_APP }}
|
||||||
|
MJML_PRIVATE: ${{ secrets.MJML_PRIVATE }}
|
||||||
|
run: make clean generate compile
|
||||||
|
|
||||||
- name: Upload Compiled Frontend (rice-box.go)
|
- name: Upload Compiled Frontend (rice-box.go)
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
|
@ -98,8 +100,22 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Set Linux Build Flags
|
- name: Set Linux Build Flags
|
||||||
if: matrix.platform != 'darwin'
|
if: matrix.platform == 'linux'
|
||||||
run: echo ::set-env name=BUILD_FLAGS::'-extldflags -static'
|
run: |
|
||||||
|
echo ::set-env name=BUILD_FLAGS::'-extldflags -static'
|
||||||
|
echo ::set-env name=XGO_TAGS::'netgo,osusergo,linux,sqlite_omit_load_extension'
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Set Darwin Build Flags
|
||||||
|
if: matrix.platform == 'darwin'
|
||||||
|
run: echo ::set-env name=XGO_TAGS::'netgo,osusergo,darwin,sqlite_omit_load_extension'
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Set Windows Build Flags
|
||||||
|
if: matrix.platform == 'windows'
|
||||||
|
run: |
|
||||||
|
echo ::set-env name=BUILD_FLAGS::'-extldflags -static'
|
||||||
|
echo ::set-env name=XGO_TAGS::'netgo,osusergo,sqlite_omit_load_extension'
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Build ${{ matrix.platform }}/${{ matrix.arch }}
|
- name: Build ${{ matrix.platform }}/${{ matrix.arch }}
|
||||||
|
@ -117,6 +133,7 @@ jobs:
|
||||||
x: false
|
x: false
|
||||||
pkg: cmd
|
pkg: cmd
|
||||||
buildmode: pie
|
buildmode: pie
|
||||||
|
tags: ${{ env.XGO_TAGS }}
|
||||||
ldflags: -s -w -X main.VERSION=${{ env.VERSION }} -X main.COMMIT=${{ env.COMMIT }} ${{ env.BUILD_FLAGS }}
|
ldflags: -s -w -X main.VERSION=${{ env.VERSION }} -X main.COMMIT=${{ env.COMMIT }} ${{ env.BUILD_FLAGS }}
|
||||||
|
|
||||||
- name: Compress Linux Builds
|
- name: Compress Linux Builds
|
||||||
|
@ -262,6 +279,10 @@ jobs:
|
||||||
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
|
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
|
||||||
GOTIFY_URL: ${{ secrets.GOTIFY_URL }}
|
GOTIFY_URL: ${{ secrets.GOTIFY_URL }}
|
||||||
GOTIFY_TOKEN: ${{ secrets.GOTIFY_TOKEN }}
|
GOTIFY_TOKEN: ${{ secrets.GOTIFY_TOKEN }}
|
||||||
|
SNS_TOKEN: ${{ secrets.SNS_TOKEN }}
|
||||||
|
SNS_SECRET: ${{ secrets.SNS_SECRET }}
|
||||||
|
SNS_REGION: ${{ secrets.SNS_REGION }}
|
||||||
|
SNS_TOPIC: ${{ secrets.SNS_TOPIC }}
|
||||||
|
|
||||||
- name: Coveralls Testing Coverage
|
- name: Coveralls Testing Coverage
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -42,7 +42,9 @@ jobs:
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ env.VERSION }}
|
VERSION: ${{ env.VERSION }}
|
||||||
COMMIT: ${{ github.sha }}
|
COMMIT: ${{ github.sha }}
|
||||||
run: make clean compile
|
MJML_APP: ${{ secrets.MJML_APP }}
|
||||||
|
MJML_PRIVATE: ${{ secrets.MJML_PRIVATE }}
|
||||||
|
run: make clean generate compile
|
||||||
|
|
||||||
- name: Upload Compiled Frontend (rice-box.go)
|
- name: Upload Compiled Frontend (rice-box.go)
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
|
@ -98,8 +100,22 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Set Linux Build Flags
|
- name: Set Linux Build Flags
|
||||||
if: matrix.platform != 'darwin'
|
if: matrix.platform == 'linux'
|
||||||
run: echo ::set-env name=BUILD_FLAGS::'-extldflags -static'
|
run: |
|
||||||
|
echo ::set-env name=BUILD_FLAGS::'-extldflags -static'
|
||||||
|
echo ::set-env name=XGO_TAGS::'netgo,osusergo,linux,sqlite_omit_load_extension'
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Set Darwin Build Flags
|
||||||
|
if: matrix.platform == 'darwin'
|
||||||
|
run: echo ::set-env name=XGO_TAGS::'netgo,osusergo,darwin,sqlite_omit_load_extension'
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Set Windows Build Flags
|
||||||
|
if: matrix.platform == 'windows'
|
||||||
|
run: |
|
||||||
|
echo ::set-env name=BUILD_FLAGS::'-extldflags -static'
|
||||||
|
echo ::set-env name=XGO_TAGS::'netgo,osusergo,sqlite_omit_load_extension'
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Build ${{ matrix.platform }}/${{ matrix.arch }}
|
- name: Build ${{ matrix.platform }}/${{ matrix.arch }}
|
||||||
|
@ -117,6 +133,7 @@ jobs:
|
||||||
x: false
|
x: false
|
||||||
pkg: cmd
|
pkg: cmd
|
||||||
buildmode: pie
|
buildmode: pie
|
||||||
|
tags: ${{ env.XGO_TAGS }}
|
||||||
ldflags: -s -w -X main.VERSION=${{ env.VERSION }} -X main.COMMIT=${{ env.COMMIT }} ${{ env.BUILD_FLAGS }}
|
ldflags: -s -w -X main.VERSION=${{ env.VERSION }} -X main.COMMIT=${{ env.COMMIT }} ${{ env.BUILD_FLAGS }}
|
||||||
|
|
||||||
- name: Compress Linux Builds
|
- name: Compress Linux Builds
|
||||||
|
@ -262,6 +279,10 @@ jobs:
|
||||||
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
|
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
|
||||||
GOTIFY_URL: ${{ secrets.GOTIFY_URL }}
|
GOTIFY_URL: ${{ secrets.GOTIFY_URL }}
|
||||||
GOTIFY_TOKEN: ${{ secrets.GOTIFY_TOKEN }}
|
GOTIFY_TOKEN: ${{ secrets.GOTIFY_TOKEN }}
|
||||||
|
SNS_TOKEN: ${{ secrets.SNS_TOKEN }}
|
||||||
|
SNS_SECRET: ${{ secrets.SNS_SECRET }}
|
||||||
|
SNS_REGION: ${{ secrets.SNS_REGION }}
|
||||||
|
SNS_TOPIC: ${{ secrets.SNS_TOPIC }}
|
||||||
|
|
||||||
- name: Coveralls Testing Coverage
|
- name: Coveralls Testing Coverage
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -38,3 +38,4 @@ tmp
|
||||||
/frontend/cypress/screenshots/
|
/frontend/cypress/screenshots/
|
||||||
/frontend/cypress/videos/
|
/frontend/cypress/videos/
|
||||||
services.yml
|
services.yml
|
||||||
|
statping.wiki
|
||||||
|
|
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,3 +1,17 @@
|
||||||
|
# 0.90.64 (08-18-2020)
|
||||||
|
- Modified max-width for container to 1012px, larger UI
|
||||||
|
- Added failure sparklines in the Services list view
|
||||||
|
- Added "Update Available" alert on the top of Settings if new version is available
|
||||||
|
- Added Version and Github Commit hash to left navigation on Settings page
|
||||||
|
- Added "reason" for failures (will be used for more custom notification messages) [regex, lookup, timeout, connection, close, status_code]
|
||||||
|
- Added Help page that is generated from Statping's Wiki repo on build
|
||||||
|
- Modified Service Group failures on index page to show 90 days of failures
|
||||||
|
- Modified Service view page, updated Latency and Ping charts, added failures below
|
||||||
|
- Modified Service chart on index page to show ping data along with latency
|
||||||
|
- Added AWS SNS Notifier
|
||||||
|
- Modified dashboard services UI
|
||||||
|
- Modified service.Failures API to include 32 failures (max)
|
||||||
|
|
||||||
# 0.90.63 (08-17-2020)
|
# 0.90.63 (08-17-2020)
|
||||||
- Modified build process to use xgo for all arch builds
|
- Modified build process to use xgo for all arch builds
|
||||||
- Modified Statping's Push Notifications server notifier to match with Firebase/gorush params
|
- Modified Statping's Push Notifications server notifier to match with Firebase/gorush params
|
||||||
|
|
13
Makefile
13
Makefile
|
@ -21,14 +21,14 @@ test: clean compile
|
||||||
go test -v -p=1 -ldflags="-X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -coverprofile=coverage.out ./...
|
go test -v -p=1 -ldflags="-X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -coverprofile=coverage.out ./...
|
||||||
|
|
||||||
build: clean
|
build: clean
|
||||||
go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -o statping --tags "netgo linux" ./cmd
|
CGO_ENABLED=1 go build -a -ldflags "-s -w -X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -o statping --tags "netgo osusergo" ./cmd
|
||||||
|
|
||||||
go-build: clean
|
go-build: clean
|
||||||
rm -rf source/dist
|
rm -rf source/dist
|
||||||
rm -rf source/rice-box.go
|
rm -rf source/rice-box.go
|
||||||
wget https://assets.statping.com/source.tar.gz
|
wget https://assets.statping.com/source.tar.gz
|
||||||
tar -xvf source.tar.gz
|
tar -xvf source.tar.gz
|
||||||
go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -o statping --tags "netgo" ./cmd
|
go build -a -ldflags "-s -w -extldflags -static -X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT}" -o statping --tags "netgo osusergo" ./cmd
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
go fmt ./...
|
go fmt ./...
|
||||||
|
@ -76,6 +76,7 @@ test-deps:
|
||||||
go get github.com/GeertJohan/go.rice/rice
|
go get github.com/GeertJohan/go.rice/rice
|
||||||
go get github.com/mattn/go-sqlite3
|
go get github.com/mattn/go-sqlite3
|
||||||
go install github.com/mattn/go-sqlite3
|
go install github.com/mattn/go-sqlite3
|
||||||
|
go install github.com/wellington/go-libsass
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
go get -d -v -t ./...
|
go get -d -v -t ./...
|
||||||
|
@ -420,5 +421,13 @@ check:
|
||||||
# sentry-cli releases set-commits --auto $VERSION
|
# sentry-cli releases set-commits --auto $VERSION
|
||||||
# sentry-cli releases files $VERSION upload-sourcemaps dist
|
# sentry-cli releases files $VERSION upload-sourcemaps dist
|
||||||
|
|
||||||
|
gen_help:
|
||||||
|
for file in ./statping.wiki/*.md
|
||||||
|
do
|
||||||
|
# convert each file to html and place it in the html directory
|
||||||
|
# --gfm == use github flavoured markdown
|
||||||
|
marked -o html/$file.html $file --gfm
|
||||||
|
done
|
||||||
|
|
||||||
.PHONY: all check build certs multiarch install-darwin go-build build-all buildx-base buildx-dev buildx-latest 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
|
.PHONY: all check build certs multiarch install-darwin go-build build-all buildx-base buildx-dev buildx-latest 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
|
.SILENT: travis_s3_creds
|
||||||
|
|
|
@ -29,10 +29,10 @@ var (
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
stopped = make(chan bool, 1)
|
stopped = make(chan bool, 1)
|
||||||
core.New(VERSION)
|
core.New(VERSION, COMMIT)
|
||||||
utils.InitEnvs()
|
utils.InitEnvs()
|
||||||
configs.Version = VERSION
|
utils.Params.Set("VERSION", VERSION)
|
||||||
configs.Commit = COMMIT
|
utils.Params.Set("COMMIT", COMMIT)
|
||||||
|
|
||||||
rootCmd.AddCommand(versionCmd)
|
rootCmd.AddCommand(versionCmd)
|
||||||
rootCmd.AddCommand(updateCmd)
|
rootCmd.AddCommand(updateCmd)
|
||||||
|
@ -161,7 +161,7 @@ func InitApp() error {
|
||||||
// start routine to delete old records (failures, hits)
|
// start routine to delete old records (failures, hits)
|
||||||
go database.Maintenance()
|
go database.Maintenance()
|
||||||
// init Sentry error monitoring (its useful)
|
// init Sentry error monitoring (its useful)
|
||||||
utils.SentryInit(&VERSION, core.App.AllowReports.Bool)
|
utils.SentryInit(core.App.AllowReports.Bool)
|
||||||
core.App.Setup = true
|
core.App.Setup = true
|
||||||
core.App.Started = utils.Now()
|
core.App.Started = utils.Now()
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -71,13 +71,11 @@ func (t *TimeVar) ToValues() ([]*TimeValue, error) {
|
||||||
|
|
||||||
// GraphData will return all hits or failures
|
// GraphData will return all hits or failures
|
||||||
func (g *GroupQuery) GraphData(by By) ([]*TimeValue, error) {
|
func (g *GroupQuery) GraphData(by By) ([]*TimeValue, error) {
|
||||||
dbQuery := g.db.MultipleSelects(
|
g.db = g.db.MultipleSelects(
|
||||||
g.db.SelectByTime(g.Group),
|
g.db.SelectByTime(g.Group),
|
||||||
by.String(),
|
by.String(),
|
||||||
).Group("timeframe").Order("timeframe", true)
|
).Group("timeframe").Order("timeframe", true)
|
||||||
|
|
||||||
g.db = dbQuery
|
|
||||||
|
|
||||||
caller, err := g.ToTimeValue()
|
caller, err := g.ToTimeValue()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -89,6 +87,9 @@ func (g *GroupQuery) GraphData(by By) ([]*TimeValue, error) {
|
||||||
return caller.ToValues()
|
return caller.ToValues()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToTimeValue will format the SQL rows into a JSON format for the API.
|
||||||
|
// [{"timestamp": "2006-01-02T15:04:05Z", "amount": 468293}]
|
||||||
|
// TODO redo this entire function, use better SQL query to group by time
|
||||||
func (g *GroupQuery) ToTimeValue() (*TimeVar, error) {
|
func (g *GroupQuery) ToTimeValue() (*TimeVar, error) {
|
||||||
rows, err := g.db.Rows()
|
rows, err := g.db.Rows()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -116,27 +117,26 @@ func (g *GroupQuery) ToTimeValue() (*TimeVar, error) {
|
||||||
func (t *TimeVar) FillMissing(current, end time.Time) ([]*TimeValue, error) {
|
func (t *TimeVar) FillMissing(current, end time.Time) ([]*TimeValue, error) {
|
||||||
timeMap := make(map[string]int64)
|
timeMap := make(map[string]int64)
|
||||||
var validSet []*TimeValue
|
var validSet []*TimeValue
|
||||||
dur := t.g.Group
|
|
||||||
for _, v := range t.data {
|
for _, v := range t.data {
|
||||||
timeMap[v.Timeframe] = v.Amount
|
timeMap[v.Timeframe] = v.Amount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
currentStr := types.FixedTime(current, t.g.Group)
|
currentStr := types.FixedTime(current, t.g.Group)
|
||||||
|
|
||||||
for {
|
|
||||||
var amount int64
|
var amount int64
|
||||||
if timeMap[currentStr] != 0 {
|
if timeMap[currentStr] != 0 {
|
||||||
amount = timeMap[currentStr]
|
amount = timeMap[currentStr]
|
||||||
}
|
}
|
||||||
|
|
||||||
validSet = append(validSet, &TimeValue{
|
validSet = append(validSet, &TimeValue{
|
||||||
Timeframe: currentStr,
|
Timeframe: currentStr,
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
})
|
})
|
||||||
|
current = current.Add(t.g.Group)
|
||||||
if current.After(end) {
|
if current.After(end) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
current = current.Add(dur)
|
|
||||||
currentStr = types.FixedTime(current, t.g.Group)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return validSet, nil
|
return validSet, nil
|
||||||
|
@ -233,10 +233,6 @@ func ParseQueries(r *http.Request, o isObject) (*GroupQuery, error) {
|
||||||
if endField == 0 {
|
if endField == 0 {
|
||||||
query.End = utils.Now()
|
query.End = utils.Now()
|
||||||
}
|
}
|
||||||
if query.End.After(utils.Now()) {
|
|
||||||
query.End = utils.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
if query.Limit != 0 {
|
if query.Limit != 0 {
|
||||||
q = q.Limit(query.Limit)
|
q = q.Limit(query.Limit)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,9 @@ func (it *Db) ParseTime(t string) (time.Time, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FormatTime returns the timestamp in the same format as the DATETIME column in database
|
||||||
func (it *Db) FormatTime(t time.Time) string {
|
func (it *Db) FormatTime(t time.Time) string {
|
||||||
switch it.Type {
|
switch it.Type {
|
||||||
case "mysql":
|
|
||||||
return t.Format("2006-01-02 15:04:05")
|
|
||||||
case "postgres":
|
case "postgres":
|
||||||
return t.Format("2006-01-02 15:04:05.999999999")
|
return t.Format("2006-01-02 15:04:05.999999999")
|
||||||
default:
|
default:
|
||||||
|
@ -30,6 +29,7 @@ func (it *Db) FormatTime(t time.Time) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SelectByTime returns an SQL query that will group "created_at" column by x seconds and returns as "timeframe"
|
||||||
func (it *Db) SelectByTime(increment time.Duration) string {
|
func (it *Db) SelectByTime(increment time.Duration) string {
|
||||||
seconds := int64(increment.Seconds())
|
seconds := int64(increment.Seconds())
|
||||||
switch it.Type {
|
switch it.Type {
|
||||||
|
@ -41,33 +41,3 @@ func (it *Db) SelectByTime(increment time.Duration) string {
|
||||||
return fmt.Sprintf("datetime((strftime('%%s', created_at) / %d) * %d, 'unixepoch') as timeframe", seconds, seconds)
|
return fmt.Sprintf("datetime((strftime('%%s', created_at) / %d) * %d, 'unixepoch') as timeframe", seconds, seconds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *Db) correctTimestamp(increment string) string {
|
|
||||||
var timestamper string
|
|
||||||
switch increment {
|
|
||||||
case "second":
|
|
||||||
timestamper = "%Y-%m-%d %H:%M:%S"
|
|
||||||
case "minute":
|
|
||||||
timestamper = "%Y-%m-%d %H:%M:00"
|
|
||||||
case "hour":
|
|
||||||
timestamper = "%Y-%m-%d %H:00:00"
|
|
||||||
case "day":
|
|
||||||
timestamper = "%Y-%m-%d 00:00:00"
|
|
||||||
case "month":
|
|
||||||
timestamper = "%Y-%m-01 00:00:00"
|
|
||||||
case "year":
|
|
||||||
timestamper = "%Y-01-01 00:00:00"
|
|
||||||
default:
|
|
||||||
timestamper = "%Y-%m-%d 00:00:00"
|
|
||||||
}
|
|
||||||
|
|
||||||
switch it.Type {
|
|
||||||
case "mysql":
|
|
||||||
case "second":
|
|
||||||
timestamper = "%Y-%m-%d %H:%i:%S"
|
|
||||||
case "minute":
|
|
||||||
timestamper = "%Y-%m-%d %H:%i:00"
|
|
||||||
}
|
|
||||||
|
|
||||||
return timestamper
|
|
||||||
}
|
|
||||||
|
|
|
@ -786,6 +786,105 @@
|
||||||
"description": "You can create custom badges with dynamic information by using [Shields.io](https://shields.io/) and parsing JSON fields with [JSONPath](http://jsonpath.com/). \n\n## Examples\n\n#### Service Uptime Percent\n<img src=\"https://img.shields.io/badge/dynamic/json?color=blue&label=%20Statping%20Uptime&query=%24.online_24_hours&url=https%3A%2F%2Fdemo.statping.com%2Fapi%2Fservices%2F2&suffix=%\">\n\n- URL: [https://demo.statping.com/api/services/2](https://demo.statping.com/api/services/2)\n- JSON Path: `$.online_24_hours`\n- Suffix: `%`\n\n```\nhttps://img.shields.io/badge/dynamic/json?color=blue&label=%20Statping%20Uptime&query=%24.online_24_hours&url=https%3A%2F%2Fdemo.statping.com%2Fapi%2Fservices%2F2&suffix=%\n```\n\n#### Count Services\n<img src=\"https://img.shields.io/badge/dynamic/json?color=purple&label=Demo%20Site&query=%24.services&url=https://demo.statping.com/health&suffix=%20services\">\n\n- URL: [https://demo.statping.com/health](https://demo.statping.com/health)\n- JSON Path: `$.services`\n- Suffix: ` services`\n\n```\nhttps://img.shields.io/badge/dynamic/json?color=purple&label=Demo%20Site&query=%24.services&url=https://demo.statping.com/health&suffix=%20services\n```"
|
"description": "You can create custom badges with dynamic information by using [Shields.io](https://shields.io/) and parsing JSON fields with [JSONPath](http://jsonpath.com/). \n\n## Examples\n\n#### Service Uptime Percent\n<img src=\"https://img.shields.io/badge/dynamic/json?color=blue&label=%20Statping%20Uptime&query=%24.online_24_hours&url=https%3A%2F%2Fdemo.statping.com%2Fapi%2Fservices%2F2&suffix=%\">\n\n- URL: [https://demo.statping.com/api/services/2](https://demo.statping.com/api/services/2)\n- JSON Path: `$.online_24_hours`\n- Suffix: `%`\n\n```\nhttps://img.shields.io/badge/dynamic/json?color=blue&label=%20Statping%20Uptime&query=%24.online_24_hours&url=https%3A%2F%2Fdemo.statping.com%2Fapi%2Fservices%2F2&suffix=%\n```\n\n#### Count Services\n<img src=\"https://img.shields.io/badge/dynamic/json?color=purple&label=Demo%20Site&query=%24.services&url=https://demo.statping.com/health&suffix=%20services\">\n\n- URL: [https://demo.statping.com/health](https://demo.statping.com/health)\n- JSON Path: `$.services`\n- Suffix: ` services`\n\n```\nhttps://img.shields.io/badge/dynamic/json?color=purple&label=Demo%20Site&query=%24.services&url=https://demo.statping.com/health&suffix=%20services\n```"
|
||||||
},
|
},
|
||||||
"response": []
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Send Push Notification",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"id": "11fe392f-3636-4d2d-84e9-1119b351d8ee",
|
||||||
|
"exec": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"type": "text/javascript"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "noauth"
|
||||||
|
},
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"notifications\": [\n {\n \"tokens\": [\"dBLB1WvTJkiWl3ZPjP0-BS:APA91bGXUbKy65CaN1XqExHXZ892jik2k9XORXSiqdUyXhcQ5RDiJ6LfXrckuH3StYJFcma4UCDr_N038YUtxYsRIHYx_8vWZ6D2uq3199LegWXGl5tz-9zk3M4WZGX8WGxIRUJ31QtW\"],\n \"platform\": 2,\n \"message\": \"This notification will go to iOS and Android platform via Firebase!\"\n }\n ]\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "https://push.statping.com/api/push",
|
||||||
|
"protocol": "https",
|
||||||
|
"host": [
|
||||||
|
"push",
|
||||||
|
"statping",
|
||||||
|
"com"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"api",
|
||||||
|
"push"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": "Send a push notification to the Statping mobile app using your Firebase device identifier."
|
||||||
|
},
|
||||||
|
"response": [
|
||||||
|
{
|
||||||
|
"name": "Send Push Notification",
|
||||||
|
"originalRequest": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"notifications\": [\n {\n \"tokens\": [\"dBLB1WvTJkiWl3ZPjP0-BS:APA91bGXUbKy65CaN1XqExHXZ892jik2k9XORXSiqdUyXhcQ5RDiJ6LfXrckuH3StYJFcma4UCDr_N038YUtxYsRIHYx_8vWZ6D2uq3199LegWXGl5tz-9zk3M4WZGX8WGxIRUJ31QtW\"],\n \"platform\": 2,\n \"message\": \"This notification will go to iOS and Android Statping App\"\n }\n ]\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "https://push.statping.com/api/push",
|
||||||
|
"protocol": "https",
|
||||||
|
"host": [
|
||||||
|
"push",
|
||||||
|
"statping",
|
||||||
|
"com"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"api",
|
||||||
|
"push"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": "OK",
|
||||||
|
"code": 200,
|
||||||
|
"_postman_previewlanguage": "json",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Length",
|
||||||
|
"value": "37"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json; charset=utf-8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Date",
|
||||||
|
"value": "Thu, 13 Aug 2020 02:24:17 GMT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "X-Gorush-Version",
|
||||||
|
"value": "No Version Provided"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cookie": [],
|
||||||
|
"body": "{\n \"counts\": 1,\n \"logs\": [],\n \"success\": \"ok\"\n}"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "This is for Statping's miscellaneous API endpoints that aren't a part of another category.",
|
"description": "This is for Statping's miscellaneous API endpoints that aren't a part of another category.",
|
||||||
|
@ -4099,7 +4198,7 @@
|
||||||
"",
|
"",
|
||||||
"pm.test(\"View All Notifiers\", function () {",
|
"pm.test(\"View All Notifiers\", function () {",
|
||||||
" var jsonData = pm.response.json();",
|
" var jsonData = pm.response.json();",
|
||||||
" pm.expect(jsonData.length).to.eql(12);",
|
" pm.expect(jsonData.length).to.eql(13);",
|
||||||
"});"
|
"});"
|
||||||
],
|
],
|
||||||
"type": "text/javascript"
|
"type": "text/javascript"
|
||||||
|
@ -4303,7 +4402,7 @@
|
||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"method\": \"slack\",\n \"host\": \"https://hooks.slack.com/services/EXAMPLEIDHERE/BV33WKP0C/MtKw3Kc8BFylTv4pohKqHtXX\",\n \"enabled\": true,\n \"limits\": 55\n}",
|
"raw": "{\n \"method\": \"slack\",\n \"host\": \"https://hooks.slack.com/services/TTJ1B90DP/RENU20O9M/9uI823SUnYBuGcxYlpSimD6H\",\n \"enabled\": true,\n \"limits\": 55\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {}
|
"raw": {}
|
||||||
}
|
}
|
||||||
|
@ -4415,7 +4514,7 @@
|
||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"method\": \"success\",\n \"notifier\": {\n \"enabled\": false,\n \"limits\": 60,\n \"method\": \"slack\",\n \"host\": \"https://webhooksurl.slack.com/***\",\n \"success_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"The service {{.Service.Name}} is back online.\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"primary\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\",\n \"failure_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\":warning: The service {{.Service.Name}} is currently offline! :warning:\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"section\\\",\\n \\\"fields\\\": [{\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Service:*\\\\n{{.Service.Name}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*URL:*\\\\n{{.Service.Domain}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Status Code:*\\\\n{{.Service.LastStatusCode}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*When:*\\\\n{{.Failure.CreatedAt}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Downtime:*\\\\n{{.Service.DowntimeAgo}}\\\"\\n }, {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"*Error:*\\\\n{{.Failure.Issue}}\\\"\\n }]\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Offline Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"danger\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\"\n }\n}",
|
"raw": "{\n \"method\": \"success\",\n \"notifier\": {\n \"enabled\": false,\n \"limits\": 60,\n \"method\": \"slack\",\n \"host\": \"https://webhooksurl.slack.com/***\",\n \"success_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"The service {{.Service.Name}} is back online.\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"primary\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\",\n \"failure_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\":warning: The service {{.Service.Name}} is currently offline! :warning:\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"section\\\",\\n \\\"fields\\\": [{\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Service:*\\\\n{{.Service.Name}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*URL:*\\\\n{{.Service.Domain}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Status Code:*\\\\n{{.Service.LastStatusCode}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*When:*\\\\n{{.Failure.CreatedAt}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Downtime:*\\\\n{{.Service.Downtime.Human}}\\\"\\n }, {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"*Error:*\\\\n{{.Failure.Issue}}\\\"\\n }]\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Offline Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"danger\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\"\n }\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {}
|
"raw": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
"@fortawesome/vue-fontawesome": "^0.1.9",
|
"@fortawesome/vue-fontawesome": "^0.1.9",
|
||||||
"@sentry/browser": "^5.20.1",
|
"@sentry/browser": "^5.20.1",
|
||||||
"@sentry/integrations": "^5.20.1",
|
"@sentry/integrations": "^5.20.1",
|
||||||
"apexcharts": "^3.15.0",
|
"apexcharts": "^3.6.6",
|
||||||
"axios": "^0.19.1",
|
"axios": "^0.19.1",
|
||||||
"codemirror-colorpicker": "^1.9.66",
|
"codemirror-colorpicker": "^1.9.66",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
|
@ -29,8 +29,9 @@
|
||||||
"js-beautify": "^1.11.0",
|
"js-beautify": "^1.11.0",
|
||||||
"querystring": "^0.2.0",
|
"querystring": "^0.2.0",
|
||||||
"sass": "^1.26.10",
|
"sass": "^1.26.10",
|
||||||
|
"semver": "^7.3.2",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-apexcharts": "^1.5.2",
|
"vue-apexcharts": "^1.6.0",
|
||||||
"vue-clipboard2": "^0.3.1",
|
"vue-clipboard2": "^0.3.1",
|
||||||
"vue-codemirror": "^4.0.6",
|
"vue-codemirror": "^4.0.6",
|
||||||
"vue-cookies": "^1.7.0",
|
"vue-cookies": "^1.7.0",
|
||||||
|
@ -75,6 +76,7 @@
|
||||||
"expect": "^25.1.0",
|
"expect": "^25.1.0",
|
||||||
"file-loader": "^5.0.2",
|
"file-loader": "^5.0.2",
|
||||||
"friendly-errors-webpack-plugin": "~1.7",
|
"friendly-errors-webpack-plugin": "~1.7",
|
||||||
|
"github-wikito-converter": "^1.5.2",
|
||||||
"html-webpack-plugin": "^4.0.0-beta.11",
|
"html-webpack-plugin": "^4.0.0-beta.11",
|
||||||
"jsdom": "^16.2.0",
|
"jsdom": "^16.2.0",
|
||||||
"jsdom-global": "^3.0.2",
|
"jsdom-global": "^3.0.2",
|
||||||
|
|
|
@ -7,7 +7,8 @@ const tokenKey = "statping_auth";
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.version = "0.90.64";
|
||||||
|
this.commit = "130cc3ede7463ec9af8d62abb84992e2a0ef453c";
|
||||||
}
|
}
|
||||||
|
|
||||||
async oauth() {
|
async oauth() {
|
||||||
|
@ -51,15 +52,15 @@ class Api {
|
||||||
return axios.post('api/services/' + data.id, data).then(response => (response.data))
|
return axios.post('api/services/' + data.id, data).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async service_hits(id, start, end, group, fill=true) {
|
async service_hits(id, start, end, group, fill = true) {
|
||||||
return axios.get('api/services/' + id + '/hits_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=' + fill).then(response => (response.data))
|
return axios.get('api/services/' + id + '/hits_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=' + fill).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async service_ping(id, start, end, group, fill=true) {
|
async service_ping(id, start, end, group, fill = true) {
|
||||||
return axios.get('api/services/' + id + '/ping_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=' + fill).then(response => (response.data))
|
return axios.get('api/services/' + id + '/ping_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=' + fill).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async service_failures_data(id, start, end, group, fill=true) {
|
async service_failures_data(id, start, end, group, fill = true) {
|
||||||
return axios.get('api/services/' + id + '/failure_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=' + fill).then(response => (response.data))
|
return axios.get('api/services/' + id + '/failure_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=' + fill).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ class Api {
|
||||||
}
|
}
|
||||||
|
|
||||||
async service_failures(id, start, end, limit = 999, offset = 0) {
|
async service_failures(id, start, end, limit = 999, offset = 0) {
|
||||||
return axios.get('api/services/' + id + '/failures?start=' + start + '&end=' + end + '&limit=' + limit+ '&offset=' + offset).then(response => (response.data))
|
return axios.get('api/services/' + id + '/failures?start=' + start + '&end=' + end + '&limit=' + limit + '&offset=' + offset).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async service_failures_delete(service) {
|
async service_failures_delete(service) {
|
||||||
|
@ -129,31 +130,31 @@ class Api {
|
||||||
}
|
}
|
||||||
|
|
||||||
async incident_updates(incident) {
|
async incident_updates(incident) {
|
||||||
return axios.get('api/incidents/'+incident.id+'/updates').then(response => (response.data))
|
return axios.get('api/incidents/' + incident.id + '/updates').then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async incident_update_create(update) {
|
async incident_update_create(update) {
|
||||||
return axios.post('api/incidents/'+update.incident+'/updates', update).then(response => (response.data))
|
return axios.post('api/incidents/' + update.incident + '/updates', update).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async incident_update_delete(update) {
|
async incident_update_delete(update) {
|
||||||
return axios.delete('api/incidents/'+update.incident+'/updates/'+update.id).then(response => (response.data))
|
return axios.delete('api/incidents/' + update.incident + '/updates/' + update.id).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async incidents_service(id) {
|
async incidents_service(id) {
|
||||||
return axios.get('api/services/'+id+'/incidents').then(response => (response.data))
|
return axios.get('api/services/' + id + '/incidents').then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async incident_create(service_id, data) {
|
async incident_create(service_id, data) {
|
||||||
return axios.post('api/services/'+service_id+'/incidents', data).then(response => (response.data))
|
return axios.post('api/services/' + service_id + '/incidents', data).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async incident_delete(incident) {
|
async incident_delete(incident) {
|
||||||
return axios.delete('api/incidents/'+incident.id).then(response => (response.data))
|
return axios.delete('api/incidents/' + incident.id).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkin(api) {
|
async checkin(api) {
|
||||||
return axios.get('api/checkins/'+api).then(response => (response.data))
|
return axios.get('api/checkins/' + api).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkin_create(data) {
|
async checkin_create(data) {
|
||||||
|
@ -161,7 +162,7 @@ class Api {
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkin_delete(checkin) {
|
async checkin_delete(checkin) {
|
||||||
return axios.delete('api/checkins/'+checkin.api_key).then(response => (response.data))
|
return axios.delete('api/checkins/' + checkin.api_key).then(response => (response.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
async messages() {
|
async messages() {
|
||||||
|
@ -270,6 +271,10 @@ class Api {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async github_release() {
|
||||||
|
return fetch('https://api.github.com/repos/statping/statping/releases/latest').then(response => response.json())
|
||||||
|
}
|
||||||
|
|
||||||
async allActions(...all) {
|
async allActions(...all) {
|
||||||
await axios.all([all])
|
await axios.all([all])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<router-view :loaded="loaded"/>
|
<router-view/>
|
||||||
<Footer v-if="$route.path !== '/setup'"/>
|
<Footer v-if="$route.path !== '/setup'"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -83,13 +83,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.chartmarker {
|
.chartmarker {
|
||||||
padding: 5px;
|
padding: 0px;
|
||||||
width: 240px;
|
width: 200px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chartmarker SPAN {
|
.chartmarker SPAN {
|
||||||
font-size: 9pt;
|
font-size: 4pt;
|
||||||
display: block;
|
display: block;
|
||||||
color: #8b8b8b;
|
color: #8b8b8b;
|
||||||
}
|
}
|
||||||
|
@ -103,11 +103,33 @@
|
||||||
background-color: #efefef;
|
background-color: #efefef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.divided {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
flex-grow: 1;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.1);
|
||||||
|
margin: 0 20px 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daily-failures {
|
||||||
|
position: absolute;
|
||||||
|
padding-top: 3px;
|
||||||
|
top: 10px;
|
||||||
|
right: 100px;
|
||||||
|
width: 300px;
|
||||||
|
height: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
.service_day {
|
.service_day {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
border-radius: 4px;
|
border-radius: 2px;
|
||||||
max-width: 25px;
|
max-width: 30px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service_day SPAN {
|
.service_day SPAN {
|
||||||
|
@ -154,10 +176,6 @@
|
||||||
padding: 5px 7px;
|
padding: 5px 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service_li {
|
|
||||||
min-height: 115px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sm {
|
.btn-sm {
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
|
@ -289,10 +307,6 @@
|
||||||
color: #a0a0a0;
|
color: #a0a0a0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service_block {
|
|
||||||
min-height: 340px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.json-field {
|
.json-field {
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,19 +92,45 @@ A:HOVER {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard_card {
|
||||||
|
background-color: $group-list-background;
|
||||||
|
box-shadow: rgba(0,0,0,.05) 0px 2px 3px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard_card:HOVER {
|
||||||
|
background-color: lighten($group-list-background, 2%) !important;
|
||||||
|
box-shadow: rgba(0,0,0,.05) 0px 1px 5px 3px;
|
||||||
|
-webkit-transition-duration: 300ms;
|
||||||
|
-moz-transition-duration: 300ms;
|
||||||
|
-o-transition-duration: 300ms;
|
||||||
|
transition-duration: 300ms;
|
||||||
|
}
|
||||||
|
|
||||||
.list-group-item {
|
.list-group-item {
|
||||||
min-height: 85pt;
|
min-height: 85pt;
|
||||||
background-color: $group-list-background;
|
background-color: $group-list-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-group-item:HOVER {
|
.list-group-item:HOVER {
|
||||||
background-color: lighten($group-list-background, 2%) !important;
|
background-color: lighten($group-list-background, 5%) !important;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px 1px;
|
||||||
|
-webkit-transition-duration: 300ms;
|
||||||
|
-moz-transition-duration: 300ms;
|
||||||
|
-o-transition-duration: 300ms;
|
||||||
|
transition-duration: 300ms;
|
||||||
|
margin-top: -1px;
|
||||||
|
margin-bottom: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-group-item A {
|
.list-group-item A {
|
||||||
color: $group-list-title;
|
color: $group-list-title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart_list_tooltip {
|
||||||
|
height: 30px;
|
||||||
|
font-size: 6pt;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
padding-bottom: 25px;
|
padding-bottom: 25px;
|
||||||
|
@ -113,16 +139,40 @@ A:HOVER {
|
||||||
background-color: $container-color;
|
background-color: $container-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login_container {
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 .5rem 1rem rgba(0, 0, 0, 0.15) !important;
|
||||||
|
background-color: $container-color;
|
||||||
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer A {
|
.footer .links {
|
||||||
color: $footer-text-color;
|
color: $footer-text-color;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer A:HOVER {
|
.footer .links:HOVER {
|
||||||
color: #6d6d6d;
|
color: #8c8c8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer .statping {
|
||||||
|
color: lighten($footer-text-color, 10%);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.footer .statping:HOVER {
|
||||||
|
color: lighten($footer-text-color, 0%);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-select {
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,10 +40,6 @@
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service_li {
|
|
||||||
border: 1px solid #f3f3f3 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding: 0px !important;
|
padding: 0px !important;
|
||||||
padding-top: 0vh !important;
|
padding-top: 0vh !important;
|
||||||
|
|
|
@ -2,20 +2,20 @@
|
||||||
$background-color: #EAEAEA;
|
$background-color: #EAEAEA;
|
||||||
$container-color: #ffffff;
|
$container-color: #ffffff;
|
||||||
$text-color: #2a2a2a;
|
$text-color: #2a2a2a;
|
||||||
$max-width: 860px;
|
$max-width: 1012px;
|
||||||
$title-color: #4e4e4e;
|
$title-color: #4e4e4e;
|
||||||
$description-color: #828282;
|
$description-color: #828282;
|
||||||
$subtitle-color: #747474;
|
$subtitle-color: #747474;
|
||||||
$mobile-card-shadow: 2px 3px 10px #b7b7b7;
|
$mobile-card-shadow: 2px 3px 10px #b7b7b7;
|
||||||
|
|
||||||
$group-list-background: #fafafa;
|
$group-list-background: #fcfcfc;
|
||||||
$group-list-title: #474747;
|
$group-list-title: #474747;
|
||||||
$navbar-color: #1c1c1c;
|
$navbar-color: #1c1c1c;
|
||||||
$navbar-background: #ffffff;
|
$navbar-background: #ffffff;
|
||||||
$input-background: #fdfdfd;
|
$input-background: #fdfdfd;
|
||||||
$input-color: #4e4e4e;
|
$input-color: #4e4e4e;
|
||||||
$input-border: 1px solid #c9c9c9;
|
$input-border: 1px solid #c9c9c9;
|
||||||
$day-success-background: #20ac13;
|
$day-success-background: #e9e9e9;
|
||||||
$day-error-background: #d50a0a;
|
$day-error-background: #d50a0a;
|
||||||
|
|
||||||
/* Status Container */
|
/* Status Container */
|
||||||
|
@ -35,7 +35,7 @@ $danger-color: #dd3545;
|
||||||
$primary-color: #3e9bff;
|
$primary-color: #3e9bff;
|
||||||
|
|
||||||
/* Footer Settings */
|
/* Footer Settings */
|
||||||
$footer-text-color: #8d8d8d;
|
$footer-text-color: #b0b0b0;
|
||||||
$nav-tab-color: #13a00d;
|
$nav-tab-color: #13a00d;
|
||||||
$footer-display: block;
|
$footer-display: block;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="col-12 mt-4 mt-md-3">
|
<div class="col-12 mt-4 mt-md-3">
|
||||||
|
|
||||||
<div class="row stats_area mb-5">
|
<div class="row stats_area mb-5">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<span class="font-6 font-weight-bold d-block">{{$store.getters.services.length}}</span>
|
<span class="font-6 font-weight-bold d-block">{{$store.getters.services.length}}</span>
|
||||||
|
@ -33,23 +32,27 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
<div v-for="(service, index) in services" class="service_block" v-bind:key="index">
|
<div v-for="(service, index) in services_no_group" class="col-12 col-md-4">
|
||||||
<ServiceInfo :service=service />
|
<ServiceInfo :service="service" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-for="group in groups">
|
||||||
|
<GroupedServices :group="group"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import isAfter from "date-fns/isAfter";
|
import GroupedServices from "@/components/Dashboard/GroupedServices";
|
||||||
import parseISO from "date-fns/parseISO";
|
|
||||||
import isBefore from "date-fns/isBefore";
|
|
||||||
|
|
||||||
const ServiceInfo = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/ServiceInfo')
|
const ServiceInfo = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/ServiceInfo')
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DashboardIndex',
|
name: 'DashboardIndex',
|
||||||
components: {
|
components: {
|
||||||
|
GroupedServices,
|
||||||
ServiceInfo
|
ServiceInfo
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -63,10 +66,15 @@
|
||||||
},
|
},
|
||||||
services() {
|
services() {
|
||||||
return this.$store.getters.services
|
return this.$store.getters.services
|
||||||
}
|
},
|
||||||
|
services_no_group() {
|
||||||
|
return this.$store.getters.servicesNoGroup
|
||||||
|
},
|
||||||
|
groups() {
|
||||||
|
return this.$store.getters.groupsInOrder
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
failuresLast24Hours() {
|
failuresLast24Hours() {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
this.services.map((s) => {
|
this.services.map((s) => {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
<th scope="col">{{$t('username')}}</th>
|
<th scope="col">{{$t('username')}}</th>
|
||||||
<th scope="col">{{$t('type')}}</th>
|
<th scope="col">{{$t('type')}}</th>
|
||||||
<th scope="col" class="d-none d-md-table-cell">{{ $t('last_login') }}</th>
|
<th scope="col" class="d-none d-md-table-cell">{{ $t('last_login') }}</th>
|
||||||
|
<th scope="col" class="d-none d-md-table-cell">Scopes</th>
|
||||||
<th scope="col"></th>
|
<th scope="col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="d-none d-md-table-cell">{{niceDate(user.updated_at)}}</td>
|
<td class="d-none d-md-table-cell">{{niceDate(user.updated_at)}}</td>
|
||||||
|
<td class="d-none d-md-table-cell">{{user.scopes}}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a @click.prevent="editUser(user, edit)" href="#" class="btn btn-outline-secondary edit-user">
|
<a @click.prevent="editUser(user, edit)" href="#" class="btn btn-outline-secondary edit-user">
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
<template>
|
||||||
|
<div class="row">
|
||||||
|
<h5 v-if="group.name" class="h5 col-12 mb-3 mt-2 text-dim">
|
||||||
|
<font-awesome-icon @click="toggle" :icon="expanded ? 'minus' : 'plus'" class="pointer mr-3"/> {{group.name}}
|
||||||
|
<span class="badge badge-success text-uppercase float-right ml-2">{{services_online.length}} online</span>
|
||||||
|
<span v-if="services_online.services_offline > 0" class="badge badge-danger text-uppercase float-right">
|
||||||
|
{{services_offline.length}} offline
|
||||||
|
</span>
|
||||||
|
</h5>
|
||||||
|
<div class="col-12 col-md-4" v-if="expanded" v-for="service in group_services">
|
||||||
|
<ServiceInfo :service="service" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const ServiceInfo = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/ServiceInfo')
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "GroupedServices",
|
||||||
|
components: {
|
||||||
|
ServiceInfo
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
expanded: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
group: {
|
||||||
|
required: true,
|
||||||
|
type: Object,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
services_online() {
|
||||||
|
return this.$store.getters.servicesInGroup(this.group.id).filter((s) => s.online)
|
||||||
|
},
|
||||||
|
services_offline() {
|
||||||
|
return this.$store.getters.servicesInGroup(this.group.id).filter((s) => !s.online)
|
||||||
|
},
|
||||||
|
group_services() {
|
||||||
|
return this.$store.getters.servicesInGroup(this.group.id)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggle() {
|
||||||
|
this.expanded = !this.expanded
|
||||||
|
},
|
||||||
|
dashboard_cookies() {
|
||||||
|
const data = [{group: 5, show: false}]
|
||||||
|
if (!this.$cookies.isKey("statping_layout")) {
|
||||||
|
this.$cookies.set("statping_layout", JSON.stringify(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,28 +1,33 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="row p-2">
|
||||||
<Loading :loading="!loaded"/>
|
|
||||||
<div v-if="loaded && !service.online" class="bg-white shadow-sm mt-3 p-3 pr-4 pl-4 col-12">
|
<div v-if="loaded && last_failure && failureBefore" class="col-12 text-danger font-2 m-0 mb-2">
|
||||||
<font-awesome-icon icon="exclamation" class="mr-3" size="1x"/>
|
<font-awesome-icon icon="exclamation" class="mr-1 text-danger font-weight-bold" size="1x"/> Recent Failure<br>
|
||||||
Last failure was {{ago(service.last_error)}} ago.
|
<span class="font-italic font-weight-light text-dim mt-1" style="max-width: 270px">
|
||||||
<code v-if="failure" class="d-block bg-light p-3 mt-3">
|
Last failure was {{ago(last_failure.created_at)}} ago. {{last_failure.issue}}
|
||||||
{{failure.issue}}
|
|
||||||
<span class="d-block text-dim float-right small mt-3 mb-1">Failure #{{failure.id}}</span>
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
<div v-if="loaded" v-for="message in $store.getters.serviceMessages(service.id)" class="bg-light shadow-sm p-3 pr-4 pl-4 col-12 mt-3">
|
|
||||||
<font-awesome-icon icon="calendar" class="mr-3" size="1x"/> {{message.description}}
|
|
||||||
<span class="d-block small text-muted mt-3">
|
|
||||||
Starts at <strong>{{niceDate(message.start_on)}}</strong> till <strong>{{niceDate(message.end_on)}}</strong>
|
|
||||||
({{dur(parseISO(message.start_on), parseISO(message.end_on))}})
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="loaded" v-for="incident in incidents" class="bg-light shadow-sm p-3 pr-4 pl-4 col-12 mt-3">
|
|
||||||
<font-awesome-icon icon="calendar" class="mr-3" size="1x"/>
|
<div v-if="loaded" v-for="message in messages" class="col-12 font-2 m-0 mb-2">
|
||||||
{{incident.title}} - {{incident.description}}
|
<font-awesome-icon icon="calendar" class="mr-1" size="1x"/> Upcoming Announcement<br>
|
||||||
<div v-for="update in incident.updates" class="d-block small">
|
<span class="font-italic font-weight-light text-dim mt-1">{{message.description}}</span>
|
||||||
<span class="font-weight-bold text-capitalize">{{update.type}}</span> - {{update.message}}
|
<span class="font-0 text-dim float-right font-weight-light mt-1">@ <strong>{{niceDate(message.start_on)}}</strong>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="loaded" v-for="incident in incidents" class="col-12 font-2 m-0 mb-2">
|
||||||
|
<font-awesome-icon icon="bullhorn" class="mr-1" size="1x"/>Recent Incident<br>
|
||||||
|
<span class="font-italic font-weight-light text-dim mt-1" style="max-width: 270px">{{incident.title}} - {{incident.description}}</span>
|
||||||
|
<span class="font-0 text-dim float-right font-weight-light mt-1">@ <strong>{{niceDate(incident.created_at)}}</strong></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="success_event && !failureBefore" class="col-12 font-2 m-0 mb-2">
|
||||||
|
<span class="text-success"><font-awesome-icon icon="check" class="mr-1" size="1x"/>No New Events</span>
|
||||||
|
<span class="font-italic d-inline-block text-truncate text-dim mt-1" style="max-width: 270px">
|
||||||
|
Last failure was {{ago(service.last_error)}} ago.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -34,7 +39,6 @@ export default {
|
||||||
name: "ServiceEvents",
|
name: "ServiceEvents",
|
||||||
components: {
|
components: {
|
||||||
Loading
|
Loading
|
||||||
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
service: {
|
service: {
|
||||||
|
@ -45,7 +49,6 @@ name: "ServiceEvents",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
incidents: null,
|
incidents: null,
|
||||||
failure: null,
|
|
||||||
loaded: false,
|
loaded: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -53,16 +56,28 @@ name: "ServiceEvents",
|
||||||
this.load()
|
this.load()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
last_failure() {
|
||||||
|
if (!this.service.failures) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return this.service.failures[0]
|
||||||
|
},
|
||||||
|
failureBefore() {
|
||||||
|
return this.isAfter(this.parseISO(this.service.last_error), this.nowSubtract(43200).toISOString())
|
||||||
|
},
|
||||||
messages() {
|
messages() {
|
||||||
return this.$store.getters.serviceMessages(this.service.id)
|
return this.$store.getters.serviceMessages(this.service.id)
|
||||||
|
},
|
||||||
|
success_event() {
|
||||||
|
if (this.service.online && this.service.messages.length === 0 && this.service.incidents.length === 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async load() {
|
async load() {
|
||||||
this.loaded = false
|
this.loaded = false
|
||||||
if (!this.service.online) {
|
|
||||||
await this.getFailure()
|
|
||||||
}
|
|
||||||
await this.getMessages()
|
await this.getMessages()
|
||||||
await this.getIncidents()
|
await this.getIncidents()
|
||||||
this.loaded = true
|
this.loaded = true
|
||||||
|
@ -70,10 +85,6 @@ name: "ServiceEvents",
|
||||||
async getMessages() {
|
async getMessages() {
|
||||||
// this.messages = this.$store.getters.serviceMessages(this.service.id)
|
// this.messages = this.$store.getters.serviceMessages(this.service.id)
|
||||||
},
|
},
|
||||||
async getFailure() {
|
|
||||||
const f = await Api.service_failures(this.service.id, null, null, 1)
|
|
||||||
this.failure = f[0]
|
|
||||||
},
|
|
||||||
async getIncidents() {
|
async getIncidents() {
|
||||||
this.incidents = await Api.incidents_service(this.service.id)
|
this.incidents = await Api.incidents_service(this.service.id)
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,64 +1,56 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="card mb-4" :class="{'offline-card': !service.online}">
|
<div class="dashboard_card card mb-4" :class="{'offline-card': !service.online}">
|
||||||
<div class="card-header pb-1">
|
<div class="card-header pb-1">
|
||||||
<h4 v-observe-visibility="setVisible">
|
<h6 v-observe-visibility="setVisible">
|
||||||
<router-link :to="serviceLink(service)">{{service.name}}</router-link>
|
<router-link :to="serviceLink(service)" class="no-decoration">{{service.name}}</router-link>
|
||||||
<span class="badge float-right text-uppercase" :class="{'badge-success': service.online, 'badge-danger': !service.online}">
|
<span class="badge float-right text-uppercase" :class="{'badge-success': service.online, 'badge-danger': !service.online}">
|
||||||
{{service.online ? $t('online') : $t('offline')}}
|
{{service.online ? $t('online') : $t('offline')}}
|
||||||
</span>
|
</span>
|
||||||
</h4>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<transition name="fade">
|
<div v-if="loaded" class="row pl-2">
|
||||||
<div v-if="loaded" class="row pl-2 pr-2">
|
<div class="col-md-6 col-sm-12 pl-2 mt-2 mt-md-0 mb-3">
|
||||||
<div class="col-md-6 col-sm-12 mt-2 mt-md-0 mb-3">
|
|
||||||
<ServiceSparkLine :title="set2_name" subtitle="Latency Last 24 Hours" :series="set2"/>
|
<ServiceSparkLine :title="set2_name" subtitle="Latency Last 24 Hours" :series="set2"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-sm-12 mt-4 mt-md-0 mb-3">
|
<div class="col-md-6 col-sm-12 pl-0 mt-4 mt-md-0 mb-3">
|
||||||
<ServiceSparkLine :title="set1_name" subtitle="Latency Last 7 Days" :series="set1"/>
|
<ServiceSparkLine :title="set1_name" subtitle="Latency Last 7 Days" :series="set1"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 mt-2 mt-md-0 mb-3">
|
|
||||||
<ServiceEvents :service="service"/>
|
<ServiceEvents :service="service"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="row mb-5">
|
||||||
|
<div class="col-12 col-md-6 text-center">
|
||||||
|
<font-awesome-icon icon="circle-notch" class="text-dim" size="2x" spin/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="row mt-5 mb-5 pt-5 pb-5">
|
<div class="col-12 col-md-6 text-center text-dim">
|
||||||
<div class="col-6 text-center text-muted">
|
<font-awesome-icon icon="circle-notch" class="text-dim" size="2x" spin/>
|
||||||
<font-awesome-icon icon="circle-notch" size="3x" spin/>
|
|
||||||
</div>
|
|
||||||
<div class="col-6 text-center text-muted">
|
|
||||||
<font-awesome-icon icon="circle-notch" size="3x" spin/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<div class="col-5 pr-0">
|
||||||
|
<span class="small text-dim"> {{ hoverbtn }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-md-3 mb-2 mb-md-0">
|
<div class="col-7 pr-2 pl-0">
|
||||||
<router-link :to="{path: `/dashboard/service/${service.id}/incidents`, params: {id: service.id} }" class="btn btn-block btn-white text-capitalize incident">
|
<div class="btn-group float-right">
|
||||||
{{$tc('incident', 2)}}
|
<button @click="$router.push({path: `/dashboard/service/${service.id}/incidents`, params: {id: service.id}})" @mouseleave="unsetHover" @mouseover="setHover('Incidents')" class="btn btn-sm btn-white incident">
|
||||||
</router-link>
|
<font-awesome-icon icon="bullhorn"/>
|
||||||
|
</button>
|
||||||
|
<button @click="$router.push({path: `/dashboard/service/${service.id}/checkins`, params: {id: service.id}})" @mouseleave="unsetHover" @mouseover="setHover('Checkins')" class="btn btn-sm btn-white checkins">
|
||||||
|
<font-awesome-icon icon="calendar-check"/>
|
||||||
|
</button>
|
||||||
|
<button @click="$router.push({path: `/dashboard/service/${service.id}/failures`, params: {id: service.id}})" @mouseleave="unsetHover" @mouseover="setHover('Failures')" class="btn btn-sm btn-white failures">
|
||||||
|
<font-awesome-icon icon="exclamation-triangle"/> <span v-if="service.stats.failures !== 0" class="badge badge-danger ml-1">{{service.stats.failures}}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-3 mb-2 mb-md-0">
|
|
||||||
<router-link :to="{path: `/dashboard/service/${service.id}/checkins`, params: {id: service.id} }" class="btn btn-block btn-white text-capitalize checkins">
|
|
||||||
{{$tc('checkin', 2)}}
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-3 mb-2 mb-md-0">
|
|
||||||
<router-link :to="{path: `/dashboard/service/${service.id}/failures`, params: {id: service.id} }" class="btn btn-block btn-white text-capitalize failures">
|
|
||||||
{{$tc('failure', 2)}} <span class="badge badge-danger float-right mt-1">{{service.stats.failures}}</span>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-3 mb-2 mb-md-0 mt-0 mt-md-1">
|
|
||||||
<span class="float-md-right">
|
|
||||||
{{$t('uptime', [service.online_7_days])}}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span v-for="(failure, index) in failures" v-bind:key="index" class="alert alert-light">
|
<span v-for="(failure, index) in failures" v-bind:key="index" class="alert alert-light">
|
||||||
|
@ -96,6 +88,8 @@
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
uptime: null,
|
uptime: null,
|
||||||
|
hovered: false,
|
||||||
|
hoverbtn: "",
|
||||||
openTab: "",
|
openTab: "",
|
||||||
set1: [],
|
set1: [],
|
||||||
set2: [],
|
set2: [],
|
||||||
|
@ -108,8 +102,17 @@
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.unsetHover()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
setHover(name) {
|
||||||
|
this.hoverbtn = name
|
||||||
|
},
|
||||||
|
unsetHover() {
|
||||||
|
this.hoverbtn = this.$t('uptime', [this.service.online_7_days])
|
||||||
|
},
|
||||||
async setVisible(isVisible, entry) {
|
async setVisible(isVisible, entry) {
|
||||||
if (isVisible && !this.visible) {
|
if (isVisible && !this.visible) {
|
||||||
await this.loadInfo()
|
await this.loadInfo()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template v-if="series.length">
|
<template v-if="series.length">
|
||||||
<apexchart width="100%" height="180" type="bar" :options="chartOpts" :series="series"></apexchart>
|
<apexchart width="100%" height="100" type="bar" :options="chartOpts" :series="series"></apexchart>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -37,6 +37,9 @@
|
||||||
enabled: true
|
enabled: true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
showPoint: false,
|
||||||
|
fullWidth:true,
|
||||||
|
chartPadding: {top: 0,right: 0,bottom: 0,left: 0},
|
||||||
stroke: {
|
stroke: {
|
||||||
curve: 'straight'
|
curve: 'straight'
|
||||||
},
|
},
|
||||||
|
@ -50,12 +53,11 @@
|
||||||
tooltip: {
|
tooltip: {
|
||||||
theme: false,
|
theme: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
custom: function({series, seriesIndex, dataPointIndex, w}) {
|
custom: ({series, seriesIndex, dataPointIndex, w}) => {
|
||||||
let ts = w.globals.seriesX[seriesIndex][dataPointIndex];
|
let ts = w.globals.seriesX[seriesIndex][dataPointIndex];
|
||||||
const dt = new Date(ts).toLocaleDateString("en-us", timeoptions)
|
const dt = new Date(ts).toLocaleDateString("en-us", timeoptions)
|
||||||
let val = series[seriesIndex][dataPointIndex];
|
let val = series[seriesIndex][dataPointIndex];
|
||||||
val = val + " ms"
|
return `<div class="chartmarker"><span>Average Response Time: </span><span class="font-3">${this.humanTime(val)}</span><span>${dt}</span></div>`
|
||||||
return `<div class="chartmarker"><span>Average Response Time: </span><span class="font-3">${val}</span><span>${dt}</span></div>`
|
|
||||||
},
|
},
|
||||||
fixed: {
|
fixed: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -74,15 +76,16 @@
|
||||||
text: this.title,
|
text: this.title,
|
||||||
offsetX: 0,
|
offsetX: 0,
|
||||||
style: {
|
style: {
|
||||||
fontSize: '28px',
|
fontSize: '18px',
|
||||||
cssClass: 'apexcharts-yaxis-title'
|
cssClass: 'apexcharts-yaxis-title'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
subtitle: {
|
subtitle: {
|
||||||
text: this.subtitle,
|
text: this.subtitle,
|
||||||
offsetX: 0,
|
offsetX: 0,
|
||||||
|
offsetY: 20,
|
||||||
style: {
|
style: {
|
||||||
fontSize: '14px',
|
fontSize: '9px',
|
||||||
cssClass: 'apexcharts-yaxis-title'
|
cssClass: 'apexcharts-yaxis-title'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,18 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Name</th>
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col" class="d-none d-md-table-cell">Status</th>
|
||||||
<th scope="col" class="d-none d-md-table-cell">Visibility</th>
|
<th scope="col" class="d-none d-md-table-cell">Visibility</th>
|
||||||
<th scope="col" class="d-none d-md-table-cell">{{ $t('group') }}</th>
|
<th scope="col" class="d-none d-md-table-cell">{{ $t('group') }}</th>
|
||||||
|
<th scope="col" class="d-none d-md-table-cell" style="width: 130px">
|
||||||
|
Failures
|
||||||
|
<div class="btn-group float-right" role="group">
|
||||||
|
<a @click="list_timeframe='3h'" type="button" class="small" :class="{'text-success': list_timeframe==='3h', 'text-muted': list_timeframe!=='3h'}">3h</a>
|
||||||
|
<a @click="list_timeframe='12h'" type="button" class="small" :class="{'text-success': list_timeframe==='12h', 'text-muted': list_timeframe!=='12h'}">12h</a>
|
||||||
|
<a @click="list_timeframe='24h'" type="button" class="small" :class="{'text-success': list_timeframe==='24h', 'text-muted': list_timeframe!=='24h'}">24h</a>
|
||||||
|
<a @click="list_timeframe='7d'" type="button" class="small" :class="{'text-success': list_timeframe==='7d', 'text-muted': list_timeframe!=='7d'}">7d</a>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
<th scope="col"></th>
|
<th scope="col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -21,6 +31,11 @@
|
||||||
<font-awesome-icon icon="bars" class="mr-3"/>
|
<font-awesome-icon icon="bars" class="mr-3"/>
|
||||||
</span> {{service.name}}
|
</span> {{service.name}}
|
||||||
</td>
|
</td>
|
||||||
|
<td class="d-none d-md-table-cell">
|
||||||
|
<span class="badge text-uppercase" :class="{'badge-success': service.online, 'badge-danger': !service.online}">
|
||||||
|
{{service.online ? $t('online') : $t('offline')}}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td class="d-none d-md-table-cell">
|
<td class="d-none d-md-table-cell">
|
||||||
<span class="badge text-uppercase" :class="{'badge-primary': service.public, 'badge-secondary': !service.public}">
|
<span class="badge text-uppercase" :class="{'badge-primary': service.public, 'badge-secondary': !service.public}">
|
||||||
{{service.public ? $t('public') : $t('private')}}
|
{{service.public ? $t('public') : $t('private')}}
|
||||||
|
@ -31,6 +46,9 @@
|
||||||
<span class="badge badge-secondary">{{serviceGroup(service)}}</span>
|
<span class="badge badge-secondary">{{serviceGroup(service)}}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="d-none d-md-table-cell">
|
||||||
|
<ServiceSparkList :service="service" :timeframe="list_timeframe"/>
|
||||||
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button :disabled="loading" v-if="$store.state.admin" @click.prevent="goto({path: `/dashboard/edit_service/${service.id}`, params: {service: service} })" class="btn btn-sm btn-outline-secondary">
|
<button :disabled="loading" v-if="$store.state.admin" @click.prevent="goto({path: `/dashboard/edit_service/${service.id}`, params: {service: service} })" class="btn btn-sm btn-outline-secondary">
|
||||||
|
@ -53,18 +71,70 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Api from "../../API";
|
import Api from "../../API";
|
||||||
|
import ServiceSparkList from "@/components/Service/ServiceSparkList";
|
||||||
const draggable = () => import(/* webpackChunkName: "dashboard" */ 'vuedraggable')
|
const draggable = () => import(/* webpackChunkName: "dashboard" */ 'vuedraggable')
|
||||||
const ToggleSwitch = () => import(/* webpackChunkName: "dashboard" */ '../../forms/ToggleSwitch');
|
const ToggleSwitch = () => import(/* webpackChunkName: "dashboard" */ '../../forms/ToggleSwitch');
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ServicesList',
|
name: 'ServicesList',
|
||||||
components: {
|
components: {
|
||||||
|
ServiceSparkList,
|
||||||
ToggleSwitch,
|
ToggleSwitch,
|
||||||
draggable
|
draggable
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
list_timeframe: "12h",
|
||||||
|
chartOpts: {
|
||||||
|
chart: {
|
||||||
|
type: 'bar',
|
||||||
|
height: 50,
|
||||||
|
sparkline: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: 'numeric',
|
||||||
|
},
|
||||||
|
showPoint: false,
|
||||||
|
fullWidth:true,
|
||||||
|
chartPadding: {top: 0,right: 0,bottom: 0,left: 0},
|
||||||
|
stroke: {
|
||||||
|
curve: 'straight'
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
min: 0
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
colors: {
|
||||||
|
ranges: [{
|
||||||
|
from: 0,
|
||||||
|
to: 1,
|
||||||
|
color: '#39c10a'
|
||||||
|
}, {
|
||||||
|
from: 2,
|
||||||
|
to: 90,
|
||||||
|
color: '#e01a1a'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: false,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
enabled: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
@ -14,18 +14,21 @@
|
||||||
<li @click="navopen = !navopen" class="nav-item navbar-item">
|
<li @click="navopen = !navopen" class="nav-item navbar-item">
|
||||||
<router-link to="/dashboard/services" class="nav-link">{{ $t('top_nav.services') }}</router-link>
|
<router-link to="/dashboard/services" class="nav-link">{{ $t('top_nav.services') }}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="$store.state.admin" @click="navopen = !navopen" class="nav-item navbar-item">
|
<li v-if="admin" @click="navopen = !navopen" class="nav-item navbar-item">
|
||||||
<router-link to="/dashboard/users" class="nav-link">{{ $t('top_nav.users') }}</router-link>
|
<router-link to="/dashboard/users" class="nav-link">{{ $t('top_nav.users') }}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li @click="navopen = !navopen" class="nav-item navbar-item">
|
<li @click="navopen = !navopen" class="nav-item navbar-item">
|
||||||
<router-link to="/dashboard/messages" class="nav-link">{{ $t('top_nav.announcements') }}</router-link>
|
<router-link to="/dashboard/messages" class="nav-link">{{ $t('top_nav.announcements') }}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="$store.state.admin" @click="navopen = !navopen" class="nav-item navbar-item">
|
<li v-if="admin" @click="navopen = !navopen" class="nav-item navbar-item">
|
||||||
<router-link to="/dashboard/settings" class="nav-link">{{ $t('top_nav.settings') }}</router-link>
|
<router-link to="/dashboard/settings" class="nav-link">{{ $t('top_nav.settings') }}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="$store.state.admin" @click="navopen = !navopen" class="nav-item navbar-item">
|
<li v-if="admin" @click="navopen = !navopen" class="nav-item navbar-item">
|
||||||
<router-link to="/dashboard/logs" class="nav-link">{{ $t('top_nav.logs') }}</router-link>
|
<router-link to="/dashboard/logs" class="nav-link">{{ $t('top_nav.logs') }}</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
<li v-if="admin" @click="navopen = !navopen" class="nav-item navbar-item">
|
||||||
|
<router-link to="/dashboard/help" class="nav-link">{{ $t('top_nav.help') }}</router-link>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<span class="navbar-text">
|
<span class="navbar-text">
|
||||||
<a href="#" class="nav-link" @click.prevent="logout">{{ $t('top_nav.logout') }}</a>
|
<a href="#" class="nav-link" @click.prevent="logout">{{ $t('top_nav.logout') }}</a>
|
||||||
|
@ -45,6 +48,11 @@
|
||||||
navopen: false
|
navopen: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
admin() {
|
||||||
|
return this.$store.state.admin
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async logout () {
|
async logout () {
|
||||||
await Api.logout()
|
await Api.logout()
|
||||||
|
@ -57,7 +65,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
|
||||||
|
const serviceSparkLine = {
|
||||||
|
chart: {
|
||||||
|
type: 'bar',
|
||||||
|
height: 50,
|
||||||
|
sparkline: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: 'straight'
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
opacity: 0.3,
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
min: 0
|
||||||
|
},
|
||||||
|
colors: ['#b3bdc3'],
|
||||||
|
tooltip: {
|
||||||
|
theme: false,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: this.title,
|
||||||
|
offsetX: 0,
|
||||||
|
style: {
|
||||||
|
fontSize: '28px',
|
||||||
|
cssClass: 'apexcharts-yaxis-title'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
text: this.subtitle,
|
||||||
|
offsetX: 0,
|
||||||
|
style: {
|
||||||
|
fontSize: '14px',
|
||||||
|
cssClass: 'apexcharts-yaxis-title'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
ServiceList: serviceSparkLine
|
||||||
|
}
|
|
@ -1,10 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<footer>
|
<footer>
|
||||||
<div v-if="!core.footer" class="footer text-center mb-4 p-2">
|
<div v-if="!core.footer" class="footer text-center mb-4 p-2">
|
||||||
<a href="https://github.com/statping/statping" target="_blank">
|
<div class="d-block text-dim">
|
||||||
Statping {{core.version}} made with <font-awesome-icon icon="heart" class="text-danger"/>
|
<div class="mb-3">
|
||||||
</a> |
|
<router-link class="links" :to="admin ? '/dashboard' : '/login'">{{$t('top_nav.dashboard')}}</router-link>
|
||||||
<router-link :to="$store.state.admin ? '/dashboard' : '/login'">{{$t('top_nav.dashboard')}}</router-link>
|
</div>
|
||||||
|
<span class="font-1 mt-3">
|
||||||
|
<a href="https://github.com/statping/statping" class="statping" target="_blank">
|
||||||
|
Statping v{{core.version}} made with <font-awesome-icon icon="heart" class="hlight font-1"/></a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="footer text-center mb-4 p-2" v-html="core.footer"></div>
|
<div v-else class="footer text-center mb-4 p-2" v-html="core.footer"></div>
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -21,11 +26,19 @@
|
||||||
computed: {
|
computed: {
|
||||||
core() {
|
core() {
|
||||||
return this.$store.getters.core
|
return this.$store.getters.core
|
||||||
}
|
},
|
||||||
|
commit() {
|
||||||
|
return this.$store.getters.core.commit.slice(0,8)
|
||||||
|
},
|
||||||
|
admin() {
|
||||||
|
return this.$store.getters.admin
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.hlight {
|
||||||
|
color: #f6cbcb;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="col-12 full-col-12">
|
<div v-if="services.length > 0" class="col-12 full-col-12">
|
||||||
<h4 v-if="group.name !== 'Empty Group'" class="group_header mb-3 mt-4">{{group.name}}</h4>
|
<h4 v-if="group.name !== 'Empty Group'" class="group_header mb-3 mt-4">{{group.name}}</h4>
|
||||||
<div class="list-group online_list mb-4">
|
<div class="list-group online_list mb-4">
|
||||||
|
|
||||||
<div v-for="(service, index) in $store.getters.servicesInGroup(group.id)" v-bind:key="index" class="service_li list-group-item list-group-item-action">
|
<div v-for="(service, index) in services" v-bind:key="index" class="list-group-item list-group-item-action">
|
||||||
<router-link class="no-decoration font-3" :to="serviceLink(service)">{{service.name}}</router-link>
|
<router-link class="no-decoration font-3" :to="serviceLink(service)">{{service.name}}</router-link>
|
||||||
<span class="badge text-uppercase float-right" :class="{'bg-success': service.online, 'bg-danger': !service.online }">
|
<span class="badge text-uppercase float-right" :class="{'bg-success': service.online, 'bg-danger': !service.online }">
|
||||||
{{service.online ? $t('online') : $t('offline')}}
|
{{service.online ? $t('online') : $t('offline')}}
|
||||||
|
@ -30,11 +30,15 @@ export default {
|
||||||
GroupServiceFailures
|
GroupServiceFailures
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
group: Object
|
group: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
services() {
|
||||||
|
return this.$store.getters.servicesInGroup(this.group.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,17 +1,31 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="d-flex mt-3 mb-2">
|
<div v-observe-visibility="{callback: visibleChart, once: true}" v-if="!loaded" class="row">
|
||||||
<div class="flex-fill service_day" v-for="(d, index) in failureData" :class="{'day-error': d.amount > 0, 'day-success': d.amount === 0}">
|
<div class="col-12 text-center mt-3">
|
||||||
<span v-if="d.amount !== 0" class="d-none d-md-block text-center small">{{d.amount}}</span>
|
<font-awesome-icon icon="circle-notch" class="text-dim" size="2x" spin/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="loaded">
|
||||||
|
<div class="d-flex mt-3">
|
||||||
|
<div class="flex-fill service_day" v-for="(d, index) in failureData" @mouseover="mouseover(d)" @mouseout="mouseout" :class="{'day-error': d.amount > 0, 'day-success': d.amount === 0}">
|
||||||
|
<span v-if="d.amount !== 0" class="d-none d-md-block text-center small"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col-3 text-left font-2 text-muted">30 Days Ago</div>
|
<div class="col-12 no-select">
|
||||||
<div class="col-6 text-center font-2" :class="{'text-muted': service.online, 'text-danger': !service.online}">
|
<p class="divided">
|
||||||
{{service_txt}}
|
<span class="font-2 text-muted">90 Days Ago</span>
|
||||||
|
<span class="divider"></span>
|
||||||
|
<span class="text-center font-2" :class="{'text-muted': service.online, 'text-danger': !service.online}">{{service_txt}}</span>
|
||||||
|
<span class="divider"></span>
|
||||||
|
<span class="font-2 text-muted">Today</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 text-right font-2 text-muted">Today</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="daily-failures small text-right text-dim">{{hover_text}}</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -26,6 +40,9 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
failureData: [],
|
failureData: [],
|
||||||
|
hover_text: "",
|
||||||
|
loaded: false,
|
||||||
|
visible: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -40,21 +57,34 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.lastDaysFailures()
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
visibleChart(isVisible, entry) {
|
||||||
|
if (isVisible && !this.visible) {
|
||||||
|
this.visible = true
|
||||||
|
this.lastDaysFailures().then(() => this.loaded = true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mouseout() {
|
||||||
|
this.hover_text = ""
|
||||||
|
},
|
||||||
|
mouseover(e) {
|
||||||
|
let txt = `${e.amount} Failures`
|
||||||
|
if (e.amount === 0) {
|
||||||
|
txt = `No Issues`
|
||||||
|
}
|
||||||
|
this.hover_text = `${e.date.toLocaleDateString()} - ${txt}`
|
||||||
|
},
|
||||||
async lastDaysFailures() {
|
async lastDaysFailures() {
|
||||||
const start = this.nowSubtract(86400 * 30)
|
const start = this.beginningOf('day', this.nowSubtract(86400 * 90))
|
||||||
const data = await Api.service_failures_data(this.service.id, this.toUnix(start), this.toUnix(this.startToday()), "24h")
|
const end = this.endOf('tomorrow')
|
||||||
|
const data = await Api.service_failures_data(this.service.id, this.toUnix(start), this.toUnix(end), "24h", true)
|
||||||
data.forEach((d) => {
|
data.forEach((d) => {
|
||||||
let date = this.parseISO(d.timeframe)
|
let date = this.parseISO(d.timeframe)
|
||||||
this.failureData.push({month: 1, day: date.getDate(), amount: d.amount})
|
this.failureData.push({month: date.getMonth(), day: date.getDate(), date: date, amount: d.amount})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -15,7 +15,3 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div v-for="(incident, i) in incidents" class="col-12">
|
<div v-for="(incident, i) in incidents" class="col-12 mt-2">
|
||||||
<span class="braker mt-1 mb-3"></span>
|
<span class="braker mt-1 mb-3"></span>
|
||||||
<h6>{{incident.title}}
|
<h6>{{incident.title}}
|
||||||
<span class="font-2 float-right">{{niceDate(incident.created_at)}}</span>
|
<span class="font-2 float-right">{{niceDate(incident.created_at)}}</span>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="service-chart-container">
|
<div class="service-chart-container">
|
||||||
<apexchart width="100%" type="area" :options="main_chart_options" :series="main_chart"></apexchart>
|
<apexchart width="100%" height="350" type="area" :options="main_chart_options" :series="main_chart"></apexchart>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@
|
||||||
return {
|
return {
|
||||||
loading: true,
|
loading: true,
|
||||||
main_data: null,
|
main_data: null,
|
||||||
|
ping_data: null,
|
||||||
expanded_data: null,
|
expanded_data: null,
|
||||||
main_chart_options: {
|
main_chart_options: {
|
||||||
noData: {
|
noData: {
|
||||||
|
@ -51,6 +52,7 @@
|
||||||
},
|
},
|
||||||
chart: {
|
chart: {
|
||||||
id: 'mainchart',
|
id: 'mainchart',
|
||||||
|
stacked: true,
|
||||||
events: {
|
events: {
|
||||||
dataPointSelection: (event, chartContext, config) => {
|
dataPointSelection: (event, chartContext, config) => {
|
||||||
window.console.log('slect')
|
window.console.log('slect')
|
||||||
|
@ -110,7 +112,9 @@
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
labels: {
|
labels: {
|
||||||
show: true
|
formatter: (value) => {
|
||||||
|
return this.humanTime(value)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
markers: {
|
markers: {
|
||||||
|
@ -165,15 +169,15 @@
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
fill: {
|
fill: {
|
||||||
colors: ["#48d338"],
|
colors: ["#f1771f", "#48d338"],
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
type: 'solid'
|
type: 'solid'
|
||||||
},
|
},
|
||||||
stroke: {
|
stroke: {
|
||||||
show: true,
|
show: true,
|
||||||
curve: 'smooth',
|
curve: 'stepline',
|
||||||
lineCap: 'butt',
|
lineCap: 'butt',
|
||||||
colors: ["#3aa82d"],
|
colors: ["#f1771f", "#48d338"],
|
||||||
width: 2,
|
width: 2,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -227,8 +231,11 @@
|
||||||
computed: {
|
computed: {
|
||||||
main_chart () {
|
main_chart () {
|
||||||
return [{
|
return [{
|
||||||
name: this.service.name,
|
name: "latency",
|
||||||
...this.convertToChartData(this.main_data)
|
...this.convertToChartData(this.main_data)
|
||||||
|
},{
|
||||||
|
name: "ping",
|
||||||
|
...this.convertToChartData(this.ping_data)
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
expanded_chart () {
|
expanded_chart () {
|
||||||
|
@ -261,9 +268,13 @@
|
||||||
},
|
},
|
||||||
async chartHits() {
|
async chartHits() {
|
||||||
this.main_data = await this.load_hits()
|
this.main_data = await this.load_hits()
|
||||||
|
this.ping_data = await this.load_ping()
|
||||||
},
|
},
|
||||||
async load_hits(start=this.params.start, end=this.params.end, group=this.group) {
|
async load_hits(start=this.params.start, end=this.params.end, group=this.group) {
|
||||||
return await Api.service_hits(this.service.id, start, end, group, false)
|
return await Api.service_hits(this.service.id, start, end, group, false)
|
||||||
|
},
|
||||||
|
async load_ping(start=this.params.start, end=this.params.end, group=this.group) {
|
||||||
|
return await Api.service_ping(this.service.id, start, end, group, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
<template>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="text-center" style="width:210px" v-if="!loaded">
|
||||||
|
<font-awesome-icon icon="circle-notch" class="h-25 text-dim" spin/>
|
||||||
|
</div>
|
||||||
|
<apexchart v-else width="100%" height="50" type="bar" :options="chartOpts" :series="data"></apexchart>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Api from "@/API";
|
||||||
|
const timeoptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "FailuresBarChart",
|
||||||
|
props: {
|
||||||
|
service: {
|
||||||
|
required: true,
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
data: null,
|
||||||
|
loaded: false,
|
||||||
|
chartOpts: {
|
||||||
|
chart: {
|
||||||
|
type: 'bar',
|
||||||
|
height: 150,
|
||||||
|
sparkline: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
showPoint: false,
|
||||||
|
fullWidth:true,
|
||||||
|
chartPadding: {top: 0,right: 0,bottom: 0,left: 80},
|
||||||
|
stroke: {
|
||||||
|
curve: 'straight'
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
opacity: 0.4,
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
colors: {
|
||||||
|
ranges: [{
|
||||||
|
from: 0,
|
||||||
|
to: 1,
|
||||||
|
color: '#cfcfcf'
|
||||||
|
}, {
|
||||||
|
from: 2,
|
||||||
|
to: 3,
|
||||||
|
color: '#f58e49'
|
||||||
|
}, {
|
||||||
|
from: 3,
|
||||||
|
to: 20,
|
||||||
|
color: '#e01a1a'
|
||||||
|
}, {
|
||||||
|
from: 21,
|
||||||
|
to: Infinity,
|
||||||
|
color: '#9b0909'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: false,
|
||||||
|
enabled: true,
|
||||||
|
custom: ({series, seriesIndex, dataPointIndex, w}) => {
|
||||||
|
let val = series[seriesIndex][dataPointIndex];
|
||||||
|
let ts = w.globals.seriesX[seriesIndex][dataPointIndex];
|
||||||
|
const dt = new Date(ts).toLocaleDateString("en-us", timeoptions)
|
||||||
|
let ago = `${(dataPointIndex-12) * -1} hours ago`
|
||||||
|
if ((dataPointIndex-12) * -1 === 0) {
|
||||||
|
ago = `Current hour`
|
||||||
|
}
|
||||||
|
return `<div class="chart_list_tooltip font-2 mb-4">${val-1} Failures<br>${dt}</div>`
|
||||||
|
},
|
||||||
|
fixed: {
|
||||||
|
enabled: true,
|
||||||
|
position: 'topLeft',
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
formatter: (value) => { return value },
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
enabled: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.loadFailures()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
group(o, n) {
|
||||||
|
this.loaded = false
|
||||||
|
this.loadFailures()
|
||||||
|
this.loaded = true
|
||||||
|
},
|
||||||
|
start(o, n) {
|
||||||
|
this.loaded = false
|
||||||
|
this.loadFailures()
|
||||||
|
this.loaded = true
|
||||||
|
},
|
||||||
|
end(o, n) {
|
||||||
|
this.loaded = false
|
||||||
|
this.loadFailures()
|
||||||
|
this.loaded = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
convertChartData(data) {
|
||||||
|
if (!data) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let arr = []
|
||||||
|
data.forEach((d, k) => {
|
||||||
|
arr.push({
|
||||||
|
x: d.timeframe,
|
||||||
|
y: d.amount+1,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return arr
|
||||||
|
},
|
||||||
|
async loadFailures() {
|
||||||
|
this.loaded = false
|
||||||
|
const data = await Api.service_failures_data(this.service.id, this.toUnix(this.parseISO(this.start)), this.toUnix(this.parseISO(this.end)), this.group, true)
|
||||||
|
this.loaded = true
|
||||||
|
this.data = [{data: this.convertChartData(data)}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -13,7 +13,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-show="!expanded" v-observe-visibility="visibleChart" class="chart-container">
|
<div v-show="!expanded" v-observe-visibility="{callback: visibleChart, throttle: 200}" class="chart-container">
|
||||||
<ServiceChart :service="service" :visible="visible" :chart_timeframe="chartTimeframe"/>
|
<ServiceChart :service="service" :visible="visible" :chart_timeframe="chartTimeframe"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -67,18 +67,12 @@ export default {
|
||||||
name: 'ServiceBlock',
|
name: 'ServiceBlock',
|
||||||
components: { Analytics, ServiceTopStats, ServiceChart},
|
components: { Analytics, ServiceTopStats, ServiceChart},
|
||||||
props: {
|
props: {
|
||||||
in_service: {
|
service: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
service() {
|
|
||||||
return this.track_service
|
|
||||||
},
|
|
||||||
timeframepick() {
|
timeframepick() {
|
||||||
return this.timeframes.find(s => s.value === this.timeframe_val)
|
return this.timeframes.find(s => s.value === this.timeframe_val)
|
||||||
},
|
},
|
||||||
|
@ -151,14 +145,13 @@ export default {
|
||||||
value: 0,
|
value: 0,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
track_service: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
// clearInterval(this.timer_func)
|
// clearInterval(this.timer_func)
|
||||||
},
|
},
|
||||||
async created() {
|
created() {
|
||||||
this.track_service = this.in_service
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
disabled_interval(interval) {
|
disabled_interval(interval) {
|
||||||
|
@ -189,29 +182,7 @@ export default {
|
||||||
},
|
},
|
||||||
async setService() {
|
async setService() {
|
||||||
await this.$store.commit('setService', this.service)
|
await this.$store.commit('setService', this.service)
|
||||||
this.$router.push('/service/'+this.service.id, {props: {in_service: this.service}})
|
this.$router.push('/service/'+this.service.id, {props: {service: this.service}})
|
||||||
},
|
|
||||||
async showMoreStats() {
|
|
||||||
this.expanded = !this.expanded;
|
|
||||||
|
|
||||||
const failData = await Graphing.failures(this.service, 7)
|
|
||||||
this.stats.total_failures.chart = failData.data;
|
|
||||||
this.stats.total_failures.value = failData.total;
|
|
||||||
|
|
||||||
const hitsData = await Graphing.hits(this.service, 7)
|
|
||||||
|
|
||||||
this.stats.high_latency.chart = hitsData.chart;
|
|
||||||
this.stats.high_latency.value = this.humanTime(hitsData.high);
|
|
||||||
|
|
||||||
this.stats.lowest_latency.chart = hitsData.chart;
|
|
||||||
this.stats.lowest_latency.value = this.humanTime(hitsData.low);
|
|
||||||
|
|
||||||
const pingData = await Graphing.pings(this.service, 7)
|
|
||||||
this.stats.high_ping.chart = pingData.chart;
|
|
||||||
this.stats.high_ping.value = this.humanTime(pingData.high);
|
|
||||||
|
|
||||||
this.stats.low_ping.chart = pingData.chart;
|
|
||||||
this.stats.low_ping.value = this.humanTime(pingData.low);
|
|
||||||
},
|
},
|
||||||
visibleChart(isVisible, entry) {
|
visibleChart(isVisible, entry) {
|
||||||
if (isVisible && !this.visible) {
|
if (isVisible && !this.visible) {
|
||||||
|
|
|
@ -47,8 +47,14 @@
|
||||||
return {
|
return {
|
||||||
ready: false,
|
ready: false,
|
||||||
showing: false,
|
showing: false,
|
||||||
data: [],
|
data: null,
|
||||||
chartOptions: {
|
ping_data: null,
|
||||||
|
series: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
chartOptions() {
|
||||||
|
return {
|
||||||
noData: {
|
noData: {
|
||||||
text: 'Loading...'
|
text: 'Loading...'
|
||||||
},
|
},
|
||||||
|
@ -58,9 +64,20 @@
|
||||||
type: "area",
|
type: "area",
|
||||||
animations: {
|
animations: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
initialAnimation: {
|
easing: 'easeinout',
|
||||||
enabled: true
|
speed: 800,
|
||||||
}
|
animateGradually: {
|
||||||
|
enabled: false,
|
||||||
|
delay: 400,
|
||||||
|
},
|
||||||
|
dynamicAnimation: {
|
||||||
|
enabled: true,
|
||||||
|
speed: 500
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
animationDuration: 0, // duration of animations when hovering an item
|
||||||
|
},
|
||||||
|
responsiveAnimationDuration: 0,
|
||||||
},
|
},
|
||||||
selection: {
|
selection: {
|
||||||
enabled: false
|
enabled: false
|
||||||
|
@ -112,13 +129,13 @@
|
||||||
custom: ({series, seriesIndex, dataPointIndex, w}) => {
|
custom: ({series, seriesIndex, dataPointIndex, w}) => {
|
||||||
let ts = w.globals.seriesX[seriesIndex][dataPointIndex];
|
let ts = w.globals.seriesX[seriesIndex][dataPointIndex];
|
||||||
const dt = new Date(ts).toLocaleDateString("en-us", timeoptions)
|
const dt = new Date(ts).toLocaleDateString("en-us", timeoptions)
|
||||||
let val = series[seriesIndex][dataPointIndex];
|
let val = series[0][dataPointIndex];
|
||||||
if (val >= 10000) {
|
let pingVal = series[1][dataPointIndex];
|
||||||
val = Math.round(val / 1000) + " ms"
|
return `<div class="chartmarker">
|
||||||
} else {
|
<span>Average Response Time: ${this.humanTime(val)}/${this.chart_timeframe.interval}</span>
|
||||||
val = val + " μs"
|
<span>Average Ping: ${this.humanTime(pingVal)}/${this.chart_timeframe.interval}</span>
|
||||||
}
|
<span>${dt}</span>
|
||||||
return `<div class="chartmarker"><span>Average Response Time: </span><span class="font-3">${val}</span><span>${dt}</span></div>`
|
</div>`
|
||||||
},
|
},
|
||||||
fixed: {
|
fixed: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -130,7 +147,9 @@
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
formatter: (value) => { return value + " %" },
|
formatter: (value) => {
|
||||||
|
return value + " %"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
|
@ -147,20 +166,17 @@
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
fill: {
|
fill: {
|
||||||
colors: [this.service.online ? "#48d338" : "#dd3545"],
|
colors: this.service.online ? ["#3dc82f", "#48d338"] : ["#c60f20", "#dd3545"],
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
type: 'solid'
|
type: 'solid',
|
||||||
},
|
},
|
||||||
stroke: {
|
stroke: {
|
||||||
show: false,
|
show: false,
|
||||||
curve: 'smooth',
|
curve: 'smooth',
|
||||||
lineCap: 'butt',
|
lineCap: 'butt',
|
||||||
colors: [this.service.online ? "#3aa82d" : "#dd3545"],
|
colors: this.service.online ? ["#38bc2a", "#48d338"] : ["#c60f20", "#dd3545"],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
data: []
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -185,10 +201,12 @@
|
||||||
if (this.data === null && val.interval !== "5m") {
|
if (this.data === null && val.interval !== "5m") {
|
||||||
await this.chartHits({start_time: val.start_time, interval: "5m"})
|
await this.chartHits({start_time: val.start_time, interval: "5m"})
|
||||||
}
|
}
|
||||||
this.series = [{
|
this.ping_data = await Api.service_ping(this.service.id, start, end, val.interval, false)
|
||||||
name: this.service.name,
|
|
||||||
...this.convertToChartData(this.data)
|
this.series = [
|
||||||
}]
|
{name: "Latency", ...this.convertToChartData(this.data)},
|
||||||
|
{name: "Ping", ...this.convertToChartData(this.ping_data)},
|
||||||
|
]
|
||||||
this.ready = true
|
this.ready = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
<template>
|
||||||
|
<div class="text-center" style="width:210px" v-if="!loaded">
|
||||||
|
<font-awesome-icon icon="circle-notch" class="h-25 text-dim" spin/>
|
||||||
|
</div>
|
||||||
|
<apexchart v-else width="240" height="30" type="bar" :options="chartOpts" :series="data"></apexchart>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Api from "@/API";
|
||||||
|
const timeoptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ServiceSparkList",
|
||||||
|
props: {
|
||||||
|
service: {
|
||||||
|
required: true,
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
timeframe: {
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
data: null,
|
||||||
|
loaded: false,
|
||||||
|
chartOpts: {
|
||||||
|
chart: {
|
||||||
|
type: 'bar',
|
||||||
|
height: 50,
|
||||||
|
sparkline: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
showPoint: false,
|
||||||
|
fullWidth:true,
|
||||||
|
chartPadding: {top: 0,right: 0,bottom: 0,left: 0},
|
||||||
|
stroke: {
|
||||||
|
curve: 'straight'
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
opacity: 0.4,
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
min: 0,
|
||||||
|
max: 5,
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
colors: {
|
||||||
|
ranges: [{
|
||||||
|
from: 0,
|
||||||
|
to: 1,
|
||||||
|
color: '#cfcfcf'
|
||||||
|
}, {
|
||||||
|
from: 2,
|
||||||
|
to: 3,
|
||||||
|
color: '#f58e49'
|
||||||
|
}, {
|
||||||
|
from: 3,
|
||||||
|
to: 20,
|
||||||
|
color: '#e01a1a'
|
||||||
|
}, {
|
||||||
|
from: 21,
|
||||||
|
to: Infinity,
|
||||||
|
color: '#9b0909'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: false,
|
||||||
|
enabled: true,
|
||||||
|
custom: ({series, seriesIndex, dataPointIndex, w}) => {
|
||||||
|
let val = series[seriesIndex][dataPointIndex];
|
||||||
|
let ts = w.globals.seriesX[seriesIndex][dataPointIndex];
|
||||||
|
const dt = new Date(ts).toLocaleDateString("en-us", timeoptions)
|
||||||
|
let ago = `${(dataPointIndex-12) * -1} hours ago`
|
||||||
|
if ((dataPointIndex-12) * -1 === 0) {
|
||||||
|
ago = `Current hour`
|
||||||
|
}
|
||||||
|
return `<div class="chart_list_tooltip">${val-1} Failures<br>${dt}</div>`
|
||||||
|
},
|
||||||
|
fixed: {
|
||||||
|
enabled: true,
|
||||||
|
position: 'topLeft',
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
formatter: (value) => { return value },
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
enabled: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadFailures()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
timeframe(o, n) {
|
||||||
|
this.loaded = false
|
||||||
|
this.loadFailures()
|
||||||
|
this.loaded = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
convertChartData(data) {
|
||||||
|
if (!data) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let arr = []
|
||||||
|
data.forEach((d, k) => {
|
||||||
|
arr.push({
|
||||||
|
x: d.timeframe,
|
||||||
|
y: d.amount+1,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return arr
|
||||||
|
},
|
||||||
|
async loadFailures() {
|
||||||
|
this.loaded = false
|
||||||
|
let start = 43200
|
||||||
|
let group = "12h"
|
||||||
|
if (this.timeframe === "3h") {
|
||||||
|
start = 10800
|
||||||
|
group = "5m"
|
||||||
|
} else if (this.timeframe === "12h") {
|
||||||
|
start = 43200
|
||||||
|
group = "1h"
|
||||||
|
} else if (this.timeframe === "24h") {
|
||||||
|
start = 86400
|
||||||
|
group = "2h"
|
||||||
|
} else if (this.timeframe === "7d") {
|
||||||
|
start = 86400 * 7
|
||||||
|
group = "24h"
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await Api.service_failures_data(this.service.id, this.toUnix(this.nowSubtract(start)), this.toUnix(this.now()), group, true)
|
||||||
|
this.loaded = true
|
||||||
|
this.data = [{data: this.convertChartData(data)}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -2,15 +2,15 @@
|
||||||
<div>
|
<div>
|
||||||
<form @submit.prevent="login" autocomplete="on">
|
<form @submit.prevent="login" autocomplete="on">
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="username" class="col-sm-2 col-form-label">{{$t('username')}}</label>
|
<label for="username" class="col-4 col-form-label">{{$t('username')}}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-8">
|
||||||
<input @keyup="checkForm" type="text" v-model="username" autocomplete="username" name="username" class="form-control" id="username" placeholder="Username" autocorrect="off" autocapitalize="none">
|
<input @keyup="checkForm" type="text" v-model="username" autocomplete="username" name="username" class="form-control" id="username" placeholder="admin" autocorrect="off" autocapitalize="none">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="password" class="col-sm-2 col-form-label">{{$t('password')}}</label>
|
<label for="password" class="col-4 col-form-label">{{$t('password')}}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-8">
|
||||||
<input @keyup="checkForm" type="password" v-model="password" autocomplete="current-password" name="password" class="form-control" id="password" placeholder="Password">
|
<input @keyup="checkForm" type="password" v-model="password" autocomplete="current-password" name="password" class="form-control" id="password" placeholder="password123">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
@ -18,14 +18,14 @@
|
||||||
<div v-if="error" class="alert alert-danger" role="alert">
|
<div v-if="error" class="alert alert-danger" role="alert">
|
||||||
{{$t('dashboard.wrong_login')}}
|
{{$t('dashboard.wrong_login')}}
|
||||||
</div>
|
</div>
|
||||||
<button @click.prevent="login" type="submit" class="btn btn-block mb-3 btn-primary" :disabled="disabled || loading">
|
<button @click.prevent="login" type="submit" class="btn btn-block btn-primary" :disabled="disabled || loading">
|
||||||
<font-awesome-icon v-if="loading" icon="circle-notch" class="mr-2" spin/>{{loading ? $t('dashboard.loading') : $t('dashboard.sign_in')}}
|
<font-awesome-icon v-if="loading" icon="circle-notch" class="mr-2" spin/>{{loading ? $t('dashboard.loading') : $t('dashboard.sign_in')}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<a v-if="oauth && oauth.gh_client_id" @click.prevent="GHlogin" href="#" class="btn btn-block btn-outline-dark">
|
<a v-if="oauth && oauth.gh_client_id" @click.prevent="GHlogin" href="#" class="mt-4 btn btn-block btn-outline-dark">
|
||||||
<font-awesome-icon :icon="['fab', 'github']" /> Login with Github
|
<font-awesome-icon :icon="['fab', 'github']" /> Login with Github
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
<small id="interval" class="form-text text-muted">Interval to check your service state</small>
|
<small id="interval" class="form-text text-muted">Interval to check your service state</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
<input v-model="service.check_interval" type="text" name="check_interval" class="form-control">
|
<input v-model="service.check_interval" type="number" name="check_interval" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -73,9 +73,11 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="service_url" class="col-sm-4 col-form-label">Service Endpoint {{service.type === 'http' ? "(URL)" : "(Domain)"}}</label>
|
<label for="service_url" class="col-sm-4 col-form-label">
|
||||||
|
Service Endpoint {{service.type === 'http' ? "(URL)" : "(Domain)"}}
|
||||||
|
</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<input v-model="service.domain" type="text" class="form-control" id="service_url" :placeholder="service.type === 'http' ? 'https://google.com' : '192.168.1.1'" required autocapitalize="none" spellcheck="false">
|
<input v-model="service.domain" type="url" class="form-control" id="service_url" :placeholder="service.type === 'http' ? 'https://google.com' : '192.168.1.1'" required autocapitalize="none" spellcheck="false">
|
||||||
<small class="form-text text-muted">Statping will attempt to connect to this address</small>
|
<small class="form-text text-muted">Statping will attempt to connect to this address</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -110,7 +112,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
<input v-model="service.timeout" type="text" name="service_timeout" class="form-control">
|
<input v-model="service.timeout" type="number" name="service_timeout" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container col-md-7 col-sm-12 mt-2 sm-container">
|
<div class="container col-md-7 col-sm-12 mt-2 sm-container">
|
||||||
<div class="col-12 col-md-8 offset-md-2 mb-4">
|
<div class="col-12 col-md-6 offset-md-3 mb-4">
|
||||||
<img alt="Statping Setup" class="col-12 mt-5 mt-md-0" style="max-width:680px" src="banner.png">
|
<img alt="Statping Setup" class="img-fluid mt-5 mt-md-0" src="banner.png">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|
|
@ -7,6 +7,7 @@ const english = {
|
||||||
announcements: "Announcements",
|
announcements: "Announcements",
|
||||||
settings: "Settings",
|
settings: "Settings",
|
||||||
logs: "Logs",
|
logs: "Logs",
|
||||||
|
help: "Help",
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
},
|
},
|
||||||
setup: {
|
setup: {
|
||||||
|
|
|
@ -7,6 +7,7 @@ const french = {
|
||||||
announcements: "Announcements",
|
announcements: "Announcements",
|
||||||
settings: "Settings",
|
settings: "Settings",
|
||||||
logs: "Logs",
|
logs: "Logs",
|
||||||
|
help: "Help",
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
},
|
},
|
||||||
setup: {
|
setup: {
|
||||||
|
|
|
@ -7,6 +7,7 @@ const german = {
|
||||||
announcements: "Announcements",
|
announcements: "Announcements",
|
||||||
settings: "Settings",
|
settings: "Settings",
|
||||||
logs: "Logs",
|
logs: "Logs",
|
||||||
|
help: "Help",
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
},
|
},
|
||||||
setup: {
|
setup: {
|
||||||
|
|
|
@ -7,6 +7,7 @@ const russian = {
|
||||||
announcements: "Announcements",
|
announcements: "Announcements",
|
||||||
settings: "Settings",
|
settings: "Settings",
|
||||||
logs: "Logs",
|
logs: "Logs",
|
||||||
|
help: "Help",
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
},
|
},
|
||||||
setup: {
|
setup: {
|
||||||
|
|
|
@ -7,6 +7,7 @@ const spanish = {
|
||||||
announcements: "Announcements",
|
announcements: "Announcements",
|
||||||
settings: "Settings",
|
settings: "Settings",
|
||||||
logs: "Logs",
|
logs: "Logs",
|
||||||
|
help: "Help",
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
},
|
},
|
||||||
setup: {
|
setup: {
|
||||||
|
|
|
@ -37,7 +37,10 @@ Sentry.init({
|
||||||
integrations: [new Integrations.Vue({Vue, attachProps: true, logErrors: true})],
|
integrations: [new Integrations.Vue({Vue, attachProps: true, logErrors: true})],
|
||||||
});
|
});
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = process.env.NODE_ENV !== 'production'
|
||||||
|
Vue.config.devtools = process.env.NODE_ENV !== 'production'
|
||||||
|
Vue.config.performance = process.env.NODE_ENV !== 'production'
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
router,
|
router,
|
||||||
store,
|
store,
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
const { startOfToday, startOfMonth, lastDayOfMonth, subSeconds, getUnixTime, fromUnixTime, differenceInSeconds, formatDistance, addMonths, addSeconds, isWithinInterval } = require('date-fns')
|
const { startOfDay, startOfWeek, endOfMonth, startOfToday, startOfTomorrow, startOfYesterday, endOfYesterday, endOfTomorrow, endOfToday, endOfDay, startOfMonth, lastDayOfMonth, subSeconds, getUnixTime, fromUnixTime, differenceInSeconds, formatDistance, addMonths, addSeconds, isWithinInterval } = require('date-fns')
|
||||||
import formatDistanceToNow from 'date-fns/formatDistanceToNow'
|
import formatDistanceToNow from 'date-fns/formatDistanceToNow'
|
||||||
import format from 'date-fns/format'
|
import format from 'date-fns/format'
|
||||||
import parseISO from 'date-fns/parseISO'
|
import parseISO from 'date-fns/parseISO'
|
||||||
import isBefore from 'date-fns/isBefore'
|
import isBefore from 'date-fns/isBefore'
|
||||||
import isAfter from 'date-fns/isAfter'
|
import isAfter from 'date-fns/isAfter'
|
||||||
|
import { roundToNearestMinutes } from 'date-fns'
|
||||||
|
|
||||||
export default Vue.mixin({
|
export default Vue.mixin({
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -33,7 +34,7 @@ export default Vue.mixin({
|
||||||
return lastDayOfMonth(t1)
|
return lastDayOfMonth(t1)
|
||||||
},
|
},
|
||||||
nowSubtract(seconds) {
|
nowSubtract(seconds) {
|
||||||
return subSeconds(new Date(), seconds)
|
return subSeconds(this.now(), seconds)
|
||||||
},
|
},
|
||||||
isAfter(date, compare) {
|
isAfter(date, compare) {
|
||||||
return isAfter(date, parseISO(compare))
|
return isAfter(date, parseISO(compare))
|
||||||
|
@ -53,11 +54,45 @@ export default Vue.mixin({
|
||||||
parseISO(v) {
|
parseISO(v) {
|
||||||
return parseISO(v)
|
return parseISO(v)
|
||||||
},
|
},
|
||||||
|
round(minutes) {
|
||||||
|
return roundToNearestMinutes(minutes)
|
||||||
|
},
|
||||||
|
endOf(method, val) {
|
||||||
|
switch (method) {
|
||||||
|
case "day":
|
||||||
|
return endOfDay(val)
|
||||||
|
case "today":
|
||||||
|
return endOfToday()
|
||||||
|
case "tomorrow":
|
||||||
|
return endOfTomorrow()
|
||||||
|
case "yesterday":
|
||||||
|
return endOfYesterday()
|
||||||
|
case "month":
|
||||||
|
return endOfMonth(val)
|
||||||
|
}
|
||||||
|
return roundToNearestMinutes(val)
|
||||||
|
},
|
||||||
|
beginningOf(method, val) {
|
||||||
|
switch (method) {
|
||||||
|
case "day":
|
||||||
|
return startOfDay(val)
|
||||||
|
case "today":
|
||||||
|
return startOfToday()
|
||||||
|
case "tomorrow":
|
||||||
|
return startOfTomorrow()
|
||||||
|
case "yesterday":
|
||||||
|
return startOfYesterday()
|
||||||
|
case "week":
|
||||||
|
return startOfWeek()
|
||||||
|
case "month":
|
||||||
|
return startOfMonth(val)
|
||||||
|
}
|
||||||
|
return roundToNearestMinutes(val)
|
||||||
|
},
|
||||||
isZero(val) {
|
isZero(val) {
|
||||||
return getUnixTime(parseISO(val)) <= 0
|
return getUnixTime(parseISO(val)) <= 0
|
||||||
},
|
},
|
||||||
smallText(s) {
|
smallText(s) {
|
||||||
const incidents = s.incidents
|
|
||||||
if (s.online) {
|
if (s.online) {
|
||||||
return `Online, checked ${this.ago(s.last_success)} ago`
|
return `Online, checked ${this.ago(s.last_success)} ago`
|
||||||
} else {
|
} else {
|
||||||
|
@ -71,6 +106,24 @@ export default Vue.mixin({
|
||||||
return `Service has been offline for ${this.ago(s.last_success)}`
|
return `Service has been offline for ${this.ago(s.last_success)}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
round_time(frame, val) {
|
||||||
|
switch(frame) {
|
||||||
|
case "15m":
|
||||||
|
return roundToNearestMinutes(val, {nearestTo: 60 * 15})
|
||||||
|
case "30m":
|
||||||
|
return roundToNearestMinutes(val, {nearestTo: 60 * 30})
|
||||||
|
case "1h":
|
||||||
|
return roundToNearestMinutes(val, {nearestTo: 3600})
|
||||||
|
case "3h":
|
||||||
|
return roundToNearestMinutes(val, {nearestTo: 3600 * 3})
|
||||||
|
case "6h":
|
||||||
|
return roundToNearestMinutes(val, {nearestTo: 3600 * 6})
|
||||||
|
case "12h":
|
||||||
|
return roundToNearestMinutes(val, {nearestTo: 3600 * 12})
|
||||||
|
case "24h":
|
||||||
|
return roundToNearestMinutes(val, {nearestTo: 3600 * 24})
|
||||||
|
}
|
||||||
|
},
|
||||||
toUnix(val) {
|
toUnix(val) {
|
||||||
return getUnixTime(val)
|
return getUnixTime(val)
|
||||||
},
|
},
|
||||||
|
@ -96,7 +149,7 @@ export default Vue.mixin({
|
||||||
},
|
},
|
||||||
serviceLink(service) {
|
serviceLink(service) {
|
||||||
if (service.permalink) {
|
if (service.permalink) {
|
||||||
service = this.$store.getters.serviceByPermalink(service.permalink)
|
service = this.$store.getters.serviceById(service.permalink)
|
||||||
}
|
}
|
||||||
if (service === undefined || this.isEmptyObject(service)) {
|
if (service === undefined || this.isEmptyObject(service)) {
|
||||||
return `/service/0`
|
return `/service/0`
|
||||||
|
@ -177,6 +230,12 @@ export default Vue.mixin({
|
||||||
}
|
}
|
||||||
return val + " μs"
|
return val + " μs"
|
||||||
},
|
},
|
||||||
|
humanTimeNum(val) {
|
||||||
|
if (val >= 1000) {
|
||||||
|
return Math.round(val / 1000)
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
},
|
||||||
firstDayOfMonth(date) {
|
firstDayOfMonth(date) {
|
||||||
return startOfMonth(date)
|
return startOfMonth(date)
|
||||||
},
|
},
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,9 +3,18 @@
|
||||||
|
|
||||||
<Header/>
|
<Header/>
|
||||||
|
|
||||||
|
<div v-if="!loaded" class="row mt-5 mb-5">
|
||||||
|
<div class="col-12 mt-5 mb-2 text-center">
|
||||||
|
<font-awesome-icon icon="circle-notch" class="text-dim" size="2x" spin/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 text-center mt-3 mb-3">
|
||||||
|
<span class="text-dim">{{loading_text}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col-12 full-col-12">
|
<div class="col-12 full-col-12">
|
||||||
<div v-for="service in services_no_group" v-bind:key="service.id" class="list-group online_list mb-4">
|
<div v-for="service in services_no_group" v-bind:key="service.id" class="list-group online_list mb-4">
|
||||||
<div class="service_li list-group-item list-group-item-action">
|
<div class="list-group-item list-group-item-action">
|
||||||
<router-link class="no-decoration font-3" :to="serviceLink(service)">{{service.name}}</router-link>
|
<router-link class="no-decoration font-3" :to="serviceLink(service)">{{service.name}}</router-link>
|
||||||
<span class="badge float-right" :class="{'bg-success': service.online, 'bg-danger': !service.online }">{{service.online ? "ONLINE" : "OFFLINE"}}</span>
|
<span class="badge float-right" :class="{'bg-success': service.online, 'bg-danger': !service.online }">{{service.online ? "ONLINE" : "OFFLINE"}}</span>
|
||||||
<GroupServiceFailures :service="service"/>
|
<GroupServiceFailures :service="service"/>
|
||||||
|
@ -14,7 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div v-if="loaded">
|
||||||
<Group v-for="group in groups" v-bind:key="group.id" :group=group />
|
<Group v-for="group in groups" v-bind:key="group.id" :group=group />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -24,7 +33,7 @@
|
||||||
|
|
||||||
<div class="col-12 full-col-12">
|
<div class="col-12 full-col-12">
|
||||||
<div v-for="service in services" :ref="service.id" v-bind:key="service.id">
|
<div v-for="service in services" :ref="service.id" v-bind:key="service.id">
|
||||||
<ServiceBlock :in_service=service />
|
<ServiceBlock :service="service" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -52,10 +61,29 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
logged_in: false
|
logged_in: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
loading_text() {
|
||||||
|
if (this.core == null) {
|
||||||
|
return "Loading Core"
|
||||||
|
} else if (this.groups == null) {
|
||||||
|
return "Loading Groups"
|
||||||
|
} else if (this.services == null) {
|
||||||
|
return "Loading Services"
|
||||||
|
} else if (this.messages == null) {
|
||||||
|
return "Loading Announcements"
|
||||||
|
} else {
|
||||||
|
return "Completed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loaded() {
|
||||||
|
return this.core !== null && this.groups !== null && this.services !== null
|
||||||
|
},
|
||||||
|
core() {
|
||||||
|
return this.$store.getters.core
|
||||||
|
},
|
||||||
messages() {
|
messages() {
|
||||||
return this.$store.getters.messages.filter(m => this.inRange(m) && m.service === 0)
|
return this.$store.getters.messages.filter(m => this.inRange(m) && m.service === 0)
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container col-md-7 col-sm-12 mt-md-5">
|
<div class="offset-md-3 offset-lg-4 offset-0 col-lg-4 col-md-6 mt-5">
|
||||||
<div class="col-10 offset-1 col-md-8 offset-md-2 mt-md-2">
|
|
||||||
<div class="col-12 col-md-8 offset-md-2 mb-4">
|
<div class="offset-1 offset-lg-2 col-lg-8 col-10 mb-4 mb-md-3">
|
||||||
<img alt="Statping Login" class="col-12 mt-5 mt-md-0" style="max-width:650px" src="banner.png">
|
<img alt="Statping Login" class="embed-responsive" src="http://0.0.0.0:8585/banner.png">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="login_container col-12 p-4">
|
||||||
<FormLogin/>
|
<FormLogin/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container col-md-7 col-sm-12 mt-md-5">
|
<div class="container col-md-7 col-sm-12 mt-md-5">
|
||||||
<div class="col-12 mb-4">
|
|
||||||
|
<div v-if="!ready" class="row mt-5">
|
||||||
|
<div class="col-12 text-center">
|
||||||
|
<font-awesome-icon icon="circle-notch" size="3x" spin/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 text-center mt-3 mb-3">
|
||||||
|
<span class="text-muted">Loading Service</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="ready" class="col-12 mb-4">
|
||||||
<span class="mt-3 mb-3 text-white d-md-none btn d-block d-md-none text-uppercase" :class="{'bg-success': service.online, 'bg-danger': !service.online}">
|
<span class="mt-3 mb-3 text-white d-md-none btn d-block d-md-none text-uppercase" :class="{'bg-success': service.online, 'bg-danger': !service.online}">
|
||||||
{{service.online ? $t('online') : $t('offline')}}
|
{{service.online ? $t('online') : $t('offline')}}
|
||||||
</span>
|
</span>
|
||||||
|
@ -20,16 +30,16 @@
|
||||||
<div class="card-header text-capitalize">Timeframe</div>
|
<div class="card-header text-capitalize">Timeframe</div>
|
||||||
<div class="card-body pb-4">
|
<div class="card-body pb-4">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-4 font-2 mb-3 mb-md-0">
|
<div class="col">
|
||||||
<flatPickr :disabled="!loaded" @on-change="reload" v-model="start_time" :config="{ enableTime: true, altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date() }" type="text" class="form-control text-left d-block" required />
|
<flatPickr :disabled="!loaded" @on-change="reload" v-model="start_time" :config="{ wrap: true, allowInput: true, enableTime: true, dateFormat: 'Z', altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date() }" type="text" class="form-control text-left" required />
|
||||||
<small class="d-block">From {{this.format(new Date(start_time))}}</small>
|
<small class="d-block">From {{this.format(new Date(start_time))}}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4 font-2 mb-3 mb-md-0">
|
<div class="col">
|
||||||
<flatPickr :disabled="!loaded" @on-change="reload" v-model="end_time" :config="{ enableTime: true, altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date()}" type="text" class="form-control text-left" required />
|
<flatPickr :disabled="!loaded" @on-change="reload" v-model="end_time" :config="{ wrap: true, allowInput: true, enableTime: true, dateFormat: 'Z', altInput: true, altFormat: 'Y-m-d h:i K', maxDate: new Date() }" type="text" class="form-control text-left" required />
|
||||||
<small class="d-block">To {{this.format(new Date(end_time))}}</small>
|
<small class="d-block">To {{this.format(new Date(end_time))}}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4 mb-1 mb-md-0">
|
<div class="col">
|
||||||
<select :disabled="!loaded" @change="chartHits" v-model="group" class="form-control">
|
<select :disabled="!loaded" @change="chartHits(service)" v-model="group" class="form-control">
|
||||||
<option value="1m">1 Minute</option>
|
<option value="1m">1 Minute</option>
|
||||||
<option value="5m">5 Minutes</option>
|
<option value="5m">5 Minutes</option>
|
||||||
<option value="15m">15 Minute</option>
|
<option value="15m">15 Minute</option>
|
||||||
|
@ -51,12 +61,13 @@
|
||||||
<div class="card text-black-50 bg-white mt-3 mb-3">
|
<div class="card text-black-50 bg-white mt-3 mb-3">
|
||||||
<div class="card-header text-capitalize">Service Latency</div>
|
<div class="card-header text-capitalize">Service Latency</div>
|
||||||
<div v-if="loaded" class="card-body">
|
<div v-if="loaded" class="card-body">
|
||||||
<div class="row mb-5">
|
<div class="row">
|
||||||
<AdvancedChart :group="group" :updated="updated_chart" :start="start_time.toString()" :end="end_time.toString()" :service="service"/>
|
<AdvancedChart :group="group" :updated="updated_chart" :start="start_time.toString()" :end="end_time.toString()" :service="service"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-5">
|
<div>
|
||||||
<apexchart height="220" type="rangeBar" :options="timeRangeOptions" :series="uptime_data"></apexchart>
|
<FailuresBarChart :service="service" :start="start_time.toString()" :end="end_time.toString()" :group="group"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="row mt-3 mb-3">
|
<div v-else class="row mt-3 mb-3">
|
||||||
<div class="col-12 text-center">
|
<div class="col-12 text-center">
|
||||||
|
@ -91,6 +102,7 @@
|
||||||
|
|
||||||
import flatPickr from 'vue-flatpickr-component';
|
import flatPickr from 'vue-flatpickr-component';
|
||||||
import 'flatpickr/dist/flatpickr.css';
|
import 'flatpickr/dist/flatpickr.css';
|
||||||
|
import FailuresBarChart from "@/components/Service/FailuresBarChart";
|
||||||
const timeoptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
|
const timeoptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
|
||||||
|
|
||||||
const axisOptions = {
|
const axisOptions = {
|
||||||
|
@ -120,6 +132,7 @@
|
||||||
export default {
|
export default {
|
||||||
name: 'Service',
|
name: 'Service',
|
||||||
components: {
|
components: {
|
||||||
|
FailuresBarChart,
|
||||||
AdvancedChart,
|
AdvancedChart,
|
||||||
ServiceTopStats,
|
ServiceTopStats,
|
||||||
ServiceHeatmap,
|
ServiceHeatmap,
|
||||||
|
@ -130,12 +143,13 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
service: null,
|
id: null,
|
||||||
tab: "failures",
|
tab: "failures",
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
ready: true,
|
ready: false,
|
||||||
group: "1h",
|
group: "15m",
|
||||||
data: null,
|
data: null,
|
||||||
|
service: null,
|
||||||
uptime_data: null,
|
uptime_data: null,
|
||||||
loaded: false,
|
loaded: false,
|
||||||
messages: [],
|
messages: [],
|
||||||
|
@ -154,8 +168,8 @@ export default {
|
||||||
timeRangeOptions: {
|
timeRangeOptions: {
|
||||||
chart: {
|
chart: {
|
||||||
id: 'uptime',
|
id: 'uptime',
|
||||||
height: 120,
|
height: 220,
|
||||||
width: "100%",
|
width: '100%',
|
||||||
type: 'rangeBar',
|
type: 'rangeBar',
|
||||||
toolbar: {
|
toolbar: {
|
||||||
show: false
|
show: false
|
||||||
|
@ -189,7 +203,7 @@ export default {
|
||||||
type: 'datetime'
|
type: 'datetime'
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
show: false
|
show: true,
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
row: {
|
row: {
|
||||||
|
@ -256,14 +270,14 @@ export default {
|
||||||
},
|
},
|
||||||
stroke: {
|
stroke: {
|
||||||
show: false,
|
show: false,
|
||||||
curve: 'smooth',
|
curve: 'stepline',
|
||||||
lineCap: 'butt',
|
lineCap: 'butt',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
xaxis: {
|
xaxis: {
|
||||||
type: "datetime",
|
type: "datetime",
|
||||||
labels: {
|
labels: {
|
||||||
show: true
|
format: 'MM yyyy'
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
enabled: false
|
enabled: false
|
||||||
|
@ -347,6 +361,9 @@ export default {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
'$route': 'fetchData'
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
core () {
|
core () {
|
||||||
return this.$store.getters.core
|
return this.$store.getters.core
|
||||||
|
@ -354,9 +371,6 @@ export default {
|
||||||
params () {
|
params () {
|
||||||
return {start: this.toUnix(new Date(this.start_time)), end: this.toUnix(new Date(this.end_time))}
|
return {start: this.toUnix(new Date(this.start_time)), end: this.toUnix(new Date(this.end_time))}
|
||||||
},
|
},
|
||||||
id () {
|
|
||||||
return this.$route.params.id;
|
|
||||||
},
|
|
||||||
uptimeSeries () {
|
uptimeSeries () {
|
||||||
return this.timedata.series
|
return this.timedata.series
|
||||||
},
|
},
|
||||||
|
@ -370,19 +384,25 @@ export default {
|
||||||
return this.$store.getters.serviceMessages(this.service.id).filter(m => this.inRange(m))
|
return this.$store.getters.serviceMessages(this.service.id).filter(m => this.inRange(m))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
'$route': 'reload',
|
|
||||||
},
|
|
||||||
created() {
|
created() {
|
||||||
this.reload()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
async mounted() {
|
mounted() {
|
||||||
if (!this.$store.getters.service) {
|
|
||||||
// const s = await Api.service(this.id)
|
|
||||||
// this.$store.commit('setService', s)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async fetchData () {
|
||||||
|
if (!this.$route.params.id) {
|
||||||
|
this.ready = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.services = await Api.services()
|
||||||
|
await this.$store.commit("setServices", this.services)
|
||||||
|
|
||||||
|
this.service = await Api.service(this.$route.params.id)
|
||||||
|
await this.reload()
|
||||||
|
this.ready = true
|
||||||
|
},
|
||||||
async updated_chart(start, end) {
|
async updated_chart(start, end) {
|
||||||
this.loaded = false
|
this.loaded = false
|
||||||
this.start_time = start
|
this.start_time = start
|
||||||
|
@ -390,19 +410,15 @@ export default {
|
||||||
this.loaded = true
|
this.loaded = true
|
||||||
},
|
},
|
||||||
async reload() {
|
async reload() {
|
||||||
this.loaded = false
|
|
||||||
const services = await Api.services()
|
|
||||||
this.$store.commit("setServices", services)
|
|
||||||
if (this.isNumeric(this.$route.params.id)) {
|
|
||||||
this.service = this.$store.getters.serviceById(this.$route.params.id)
|
|
||||||
} else {
|
|
||||||
this.service = this.$store.getters.serviceByPermalink(this.$route.params.id)
|
|
||||||
}
|
|
||||||
await this.chartHits()
|
await this.chartHits()
|
||||||
|
await this.chartFailures()
|
||||||
await this.fetchUptime()
|
await this.fetchUptime()
|
||||||
this.loaded = true
|
this.loaded = true
|
||||||
},
|
},
|
||||||
async fetchUptime() {
|
async fetchUptime(service) {
|
||||||
|
if (service) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const uptime = await Api.service_uptime(this.service.id, this.params.start, this.params.end)
|
const uptime = await Api.service_uptime(this.service.id, this.params.start, this.params.end)
|
||||||
this.uptime_data = this.parse_uptime(uptime)
|
this.uptime_data = this.parse_uptime(uptime)
|
||||||
},
|
},
|
||||||
|
@ -438,20 +454,19 @@ export default {
|
||||||
},
|
},
|
||||||
inRange(message) {
|
inRange(message) {
|
||||||
return this.isBetween(this.now(), message.start_on, message.start_on === message.end_on ? this.maxDate().toISOString() : message.end_on)
|
return this.isBetween(this.now(), message.start_on, message.start_on === message.end_on ? this.maxDate().toISOString() : message.end_on)
|
||||||
},
|
|
||||||
async getService() {
|
|
||||||
await this.chartHits()
|
|
||||||
await this.serviceFailures()
|
|
||||||
},
|
|
||||||
async serviceFailures() {
|
|
||||||
this.failures = await Api.service_failures(this.service.id, this.params.start, this.params.end)
|
|
||||||
},
|
},
|
||||||
async chartHits(start=0, end=99999999999) {
|
async chartHits(start=0, end=99999999999) {
|
||||||
this.data = await Api.service_hits(this.service.id, this.params.start, this.params.end, this.group, false)
|
if (!this.service) {
|
||||||
if (this.data.length === 0 && this.group !== "1h") {
|
return
|
||||||
this.group = "1h"
|
|
||||||
await this.chartHits("1h")
|
|
||||||
}
|
}
|
||||||
|
this.data = await Api.service_hits(this.service.id, this.params.start, this.params.end, this.group, false)
|
||||||
|
this.ready = true
|
||||||
|
},
|
||||||
|
async chartFailures(start=0, end=99999999999) {
|
||||||
|
if (!this.service) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.failures_data = await Api.service_failures_data(this.service.id, this.params.start, this.params.end, this.group, true)
|
||||||
this.ready = true
|
this.ready = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,13 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-3 col-sm-12 mb-4 mb-md-0">
|
<div class="col-md-3 col-sm-12 mb-4 mb-md-0">
|
||||||
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
|
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
|
||||||
|
|
||||||
|
<div v-if="version_below" class="alert small text-center mt-0 pt-0 pb-0">
|
||||||
|
Update {{github.tag_name}} Available
|
||||||
|
<a href="https://github.com/statping/statping/releases/latest" class="btn btn-sm text-success mt-2">Download</a>
|
||||||
|
<a href="https://github.com/statping/statping/blob/master/CHANGELOG.md" class="btn btn-sm text-dim mt-2">Changelog</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h6 class="text-muted">{{ $t('settings.main') }}</h6>
|
<h6 class="text-muted">{{ $t('settings.main') }}</h6>
|
||||||
|
|
||||||
<a @click.prevent="changeTab" class="nav-link" v-bind:class="{active: liClass('v-pills-home-tab')}" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">
|
<a @click.prevent="changeTab" class="nav-link" v-bind:class="{active: liClass('v-pills-home-tab')}" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">
|
||||||
|
@ -48,9 +55,10 @@
|
||||||
<font-awesome-icon icon="code-branch" class="mr-3"/> {{$t('settings.repo')}}
|
<font-awesome-icon icon="code-branch" class="mr-3"/> {{$t('settings.repo')}}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="row justify-content-center mt-2">
|
<span class="small text-dim text-center mt-5">Statping v{{core.version}}<br>
|
||||||
<github-button href="https://github.com/statping/statping" data-icon="octicon-star" data-show-count="true" aria-label="Star Statping on GitHub">Star</github-button>
|
<a class="small text-muted no-decoration" v-if="core.commit" v-bind:href="`https://github.com/statping/statping/commit/${core.commit}`">{{core.commit.slice(0,8)}}</a>
|
||||||
</div>
|
</span>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -116,8 +124,8 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Api from '../API';
|
import Api from '../API';
|
||||||
import GithubButton from 'vue-github-button'
|
|
||||||
import Variables from "@/components/Dashboard/Variables";
|
import Variables from "@/components/Dashboard/Variables";
|
||||||
|
const semver = require('semver')
|
||||||
|
|
||||||
const CoreSettings = () => import(/* webpackChunkName: "dashboard" */ '@/forms/CoreSettings')
|
const CoreSettings = () => import(/* webpackChunkName: "dashboard" */ '@/forms/CoreSettings')
|
||||||
const FormIntegration = () => import(/* webpackChunkName: "dashboard" */ '@/forms/Integration')
|
const FormIntegration = () => import(/* webpackChunkName: "dashboard" */ '@/forms/Integration')
|
||||||
|
@ -130,7 +138,6 @@
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
components: {
|
components: {
|
||||||
Variables,
|
Variables,
|
||||||
GithubButton,
|
|
||||||
OAuth,
|
OAuth,
|
||||||
Cache,
|
Cache,
|
||||||
ThemeEditor,
|
ThemeEditor,
|
||||||
|
@ -141,6 +148,7 @@
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tab: "v-pills-home-tab",
|
tab: "v-pills-home-tab",
|
||||||
|
github: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -149,6 +157,12 @@
|
||||||
},
|
},
|
||||||
notifiers() {
|
notifiers() {
|
||||||
return this.$store.getters.notifiers
|
return this.$store.getters.notifiers
|
||||||
|
},
|
||||||
|
version_below() {
|
||||||
|
if (!this.github || !this.core.version) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return semver.gt(semver.coerce(this.github.tag_name), semver.coerce(this.core.version))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -159,11 +173,15 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async update() {
|
async update() {
|
||||||
const c = await Api.core()
|
|
||||||
this.$store.commit('setCore', c)
|
|
||||||
const n = await Api.notifiers()
|
|
||||||
this.$store.commit('setNotifiers', n)
|
|
||||||
this.cache = await Api.cache()
|
this.cache = await Api.cache()
|
||||||
|
await this.getGithub()
|
||||||
|
},
|
||||||
|
async getGithub() {
|
||||||
|
try {
|
||||||
|
this.github = await Api.github_release()
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
changeTab(e) {
|
changeTab(e) {
|
||||||
this.tab = e.target.id
|
this.tab = e.target.id
|
||||||
|
@ -172,19 +190,24 @@
|
||||||
return this.tab === id
|
return this.tab === id
|
||||||
},
|
},
|
||||||
async renewApiKeys() {
|
async renewApiKeys() {
|
||||||
let r = confirm("Are you sure you want to reset the API keys?");
|
let r = confirm("Are you sure you want to reset the API keys? You will be logged out.");
|
||||||
if (r === true) {
|
if (r === true) {
|
||||||
await Api.renewApiKeys()
|
await Api.renewApiKeys()
|
||||||
const core = await Api.core()
|
const core = await Api.core()
|
||||||
this.$store.commit('setCore', core)
|
this.$store.commit('setCore', core)
|
||||||
this.core = core
|
this.core = core
|
||||||
|
await this.logout()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async logout () {
|
||||||
|
await Api.logout()
|
||||||
|
this.$store.commit('setHasAllData', false)
|
||||||
|
this.$store.commit('setToken', null)
|
||||||
|
this.$store.commit('setAdmin', false)
|
||||||
|
this.$store.commit('setUser', false)
|
||||||
|
// this.$cookies.remove("statping_auth")
|
||||||
|
await this.$router.push('/logout')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ const DashboardServices = () => import(/* webpackChunkName: "dashboard" */ '@/co
|
||||||
const DashboardMessages = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/DashboardMessages')
|
const DashboardMessages = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/DashboardMessages')
|
||||||
const EditService = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/EditService')
|
const EditService = () => import(/* webpackChunkName: "dashboard" */ '@/components/Dashboard/EditService')
|
||||||
const Logs = () => import(/* webpackChunkName: "dashboard" */ '@/pages/Logs')
|
const Logs = () => import(/* webpackChunkName: "dashboard" */ '@/pages/Logs')
|
||||||
|
const Help = () => import(/* webpackChunkName: "dashboard" */ '@/pages/Help')
|
||||||
const Settings = () => import(/* webpackChunkName: "dashboard" */ '@/pages/Settings')
|
const Settings = () => import(/* webpackChunkName: "dashboard" */ '@/pages/Settings')
|
||||||
const Login = () => import(/* webpackChunkName: "index" */ '@/pages/Login')
|
const Login = () => import(/* webpackChunkName: "index" */ '@/pages/Login')
|
||||||
const Service = () => import(/* webpackChunkName: "index" */ '@/pages/Service')
|
const Service = () => import(/* webpackChunkName: "index" */ '@/pages/Service')
|
||||||
|
@ -158,7 +159,7 @@ const routes = [
|
||||||
}
|
}
|
||||||
},{
|
},{
|
||||||
path: 'help',
|
path: 'help',
|
||||||
component: Logs,
|
component: Help,
|
||||||
meta: {
|
meta: {
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
title: 'Statping - Help',
|
title: 'Statping - Help',
|
||||||
|
|
|
@ -36,6 +36,7 @@ export default new Vuex.Store({
|
||||||
getters: {
|
getters: {
|
||||||
hasAllData: state => state.hasAllData,
|
hasAllData: state => state.hasAllData,
|
||||||
hasPublicData: state => state.hasPublicData,
|
hasPublicData: state => state.hasPublicData,
|
||||||
|
admin: state => state.admin,
|
||||||
core: state => state.core,
|
core: state => state.core,
|
||||||
oauth: state => state.oauth,
|
oauth: state => state.oauth,
|
||||||
token: state => state.token,
|
token: state => state.token,
|
||||||
|
@ -69,10 +70,7 @@ export default new Vuex.Store({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
serviceById: (state) => (id) => {
|
serviceById: (state) => (id) => {
|
||||||
return state.services.find(s => s.id === id)
|
return state.services.find(s => s.permalink === id || s.id === id)
|
||||||
},
|
|
||||||
serviceByPermalink: (state) => (permalink) => {
|
|
||||||
return state.services.find(s => s.permalink === permalink)
|
|
||||||
},
|
},
|
||||||
servicesInGroup: (state) => (id) => {
|
servicesInGroup: (state) => (id) => {
|
||||||
return state.services.filter(s => s.group_id === id).sort((a, b) => a.order_id - b.order_id)
|
return state.services.filter(s => s.group_id === id).sort((a, b) => a.order_id - b.order_id)
|
||||||
|
|
|
@ -2,6 +2,9 @@ module.exports = {
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
assetsDir: 'assets',
|
assetsDir: 'assets',
|
||||||
filenameHashing: false,
|
filenameHashing: false,
|
||||||
|
productionTip: process.env.NODE_ENV !== 'production',
|
||||||
|
devtools: process.env.NODE_ENV !== 'production',
|
||||||
|
performance: process.env.NODE_ENV !== 'production',
|
||||||
devServer: {
|
devServer: {
|
||||||
disableHostCheck: true,
|
disableHostCheck: true,
|
||||||
proxyTable: {
|
proxyTable: {
|
||||||
|
|
|
@ -2034,7 +2034,7 @@ anymatch@~3.1.1:
|
||||||
normalize-path "^3.0.0"
|
normalize-path "^3.0.0"
|
||||||
picomatch "^2.0.4"
|
picomatch "^2.0.4"
|
||||||
|
|
||||||
apexcharts@^3.15.0:
|
apexcharts@^3.6.6:
|
||||||
version "3.20.0"
|
version "3.20.0"
|
||||||
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.20.0.tgz#768bb963d2bd87abe3a37a6ee35c7fc7d43bbfb7"
|
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.20.0.tgz#768bb963d2bd87abe3a37a6ee35c7fc7d43bbfb7"
|
||||||
integrity sha512-DuQ9SlFPJBJwamYudzwf/Z6KMaIRUhnVBQk/TBa3mXzN2SwS3GgGz7V39OS1GfcPlPUTTy8vXv91M8pYmfFkCg==
|
integrity sha512-DuQ9SlFPJBJwamYudzwf/Z6KMaIRUhnVBQk/TBa3mXzN2SwS3GgGz7V39OS1GfcPlPUTTy8vXv91M8pYmfFkCg==
|
||||||
|
@ -2176,6 +2176,11 @@ async@^2.6.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.14"
|
lodash "^4.17.14"
|
||||||
|
|
||||||
|
async@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9"
|
||||||
|
integrity sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=
|
||||||
|
|
||||||
asynckit@^0.4.0:
|
asynckit@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
|
@ -2338,6 +2343,11 @@ bluebird@3.7.2, bluebird@^3.1.1, bluebird@^3.5.1, bluebird@^3.5.5:
|
||||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
||||||
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
|
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
|
||||||
|
|
||||||
|
bluebird@^2.9.34:
|
||||||
|
version "2.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
|
||||||
|
integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=
|
||||||
|
|
||||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
|
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
|
||||||
version "4.11.9"
|
version "4.11.9"
|
||||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
|
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
|
||||||
|
@ -2381,6 +2391,11 @@ boolbase@^1.0.0, boolbase@~1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||||
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
|
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
|
||||||
|
|
||||||
|
bootstrap@^3.3.7:
|
||||||
|
version "3.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.4.1.tgz#c3a347d419e289ad11f4033e3c4132b87c081d72"
|
||||||
|
integrity sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==
|
||||||
|
|
||||||
brace-expansion@^1.1.7:
|
brace-expansion@^1.1.7:
|
||||||
version "1.1.11"
|
version "1.1.11"
|
||||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||||
|
@ -3104,6 +3119,11 @@ colorette@^1.2.1:
|
||||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
|
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
|
||||||
integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
|
integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
|
||||||
|
|
||||||
|
colors@1.0.x:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
|
||||||
|
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
|
||||||
|
|
||||||
combined-stream@^1.0.6, combined-stream@~1.0.6:
|
combined-stream@^1.0.6, combined-stream@~1.0.6:
|
||||||
version "1.0.8"
|
version "1.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||||
|
@ -3116,7 +3136,7 @@ commander@2.17.x:
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
|
||||||
integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
|
integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
|
||||||
|
|
||||||
commander@^2.18.0, commander@^2.19.0, commander@^2.20.0:
|
commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.8.1:
|
||||||
version "2.20.3"
|
version "2.20.3"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||||
|
@ -3622,6 +3642,11 @@ cssstyle@^2.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
cssom "~0.3.6"
|
cssom "~0.3.6"
|
||||||
|
|
||||||
|
cycle@1.0.x:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
|
||||||
|
integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI=
|
||||||
|
|
||||||
cyclist@^1.0.1:
|
cyclist@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||||
|
@ -3643,6 +3668,15 @@ data-urls@^2.0.0:
|
||||||
whatwg-mimetype "^2.3.0"
|
whatwg-mimetype "^2.3.0"
|
||||||
whatwg-url "^8.0.0"
|
whatwg-url "^8.0.0"
|
||||||
|
|
||||||
|
datauri@^1.0.5:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/datauri/-/datauri-1.1.0.tgz#c6184ff6b928ede4e41ccc23ab954c7839c4fb39"
|
||||||
|
integrity sha512-0q+cTTKx7q8eDteZRIQLTFJuiIsVing17UbWTPssY4JLSMaYsk/VKpNulBDo9NSgQWcvlPrkEHW8kUO67T/7mQ==
|
||||||
|
dependencies:
|
||||||
|
image-size "^0.6.2"
|
||||||
|
mimer "^0.3.2"
|
||||||
|
semver "^5.5.0"
|
||||||
|
|
||||||
date-fns@^2.9.0:
|
date-fns@^2.9.0:
|
||||||
version "2.15.0"
|
version "2.15.0"
|
||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.15.0.tgz#424de6b3778e4e69d3ff27046ec136af58ae5d5f"
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.15.0.tgz#424de6b3778e4e69d3ff27046ec136af58ae5d5f"
|
||||||
|
@ -3756,7 +3790,7 @@ default-gateway@^5.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
execa "^3.3.0"
|
execa "^3.3.0"
|
||||||
|
|
||||||
defaults@^1.0.3:
|
defaults@^1.0.2, defaults@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
|
resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
|
||||||
integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=
|
integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=
|
||||||
|
@ -4635,6 +4669,11 @@ extsprintf@^1.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
||||||
|
|
||||||
|
eyes@0.1.x:
|
||||||
|
version "0.1.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
|
||||||
|
integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=
|
||||||
|
|
||||||
fast-deep-equal@^3.1.1:
|
fast-deep-equal@^3.1.1:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
|
@ -4950,6 +4989,16 @@ from@~0:
|
||||||
resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe"
|
resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe"
|
||||||
integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=
|
integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=
|
||||||
|
|
||||||
|
fs-extra@^0.23.1:
|
||||||
|
version "0.23.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.23.1.tgz#6611dba6adf2ab8dc9c69fab37cddf8818157e3d"
|
||||||
|
integrity sha1-ZhHbpq3yq43Jxp+rN83fiBgVfj0=
|
||||||
|
dependencies:
|
||||||
|
graceful-fs "^4.1.2"
|
||||||
|
jsonfile "^2.1.0"
|
||||||
|
path-is-absolute "^1.0.0"
|
||||||
|
rimraf "^2.2.8"
|
||||||
|
|
||||||
fs-extra@^7.0.1:
|
fs-extra@^7.0.1:
|
||||||
version "7.0.1"
|
version "7.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
|
||||||
|
@ -5060,6 +5109,25 @@ github-buttons@^2.8.0:
|
||||||
resolved "https://registry.yarnpkg.com/github-buttons/-/github-buttons-2.14.0.tgz#32ce381651091accda09217cd7a6e4c77f91e222"
|
resolved "https://registry.yarnpkg.com/github-buttons/-/github-buttons-2.14.0.tgz#32ce381651091accda09217cd7a6e4c77f91e222"
|
||||||
integrity sha512-rAwKwFOiWoyhb3g5ZyXjI3XXprAa36jCd0tm467aEUYtiDZkqEXkepuzNg9LryLbnuLRQmcifIPTxLUBnuYpXQ==
|
integrity sha512-rAwKwFOiWoyhb3g5ZyXjI3XXprAa36jCd0tm467aEUYtiDZkqEXkepuzNg9LryLbnuLRQmcifIPTxLUBnuYpXQ==
|
||||||
|
|
||||||
|
github-wikito-converter@^1.5.2:
|
||||||
|
version "1.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/github-wikito-converter/-/github-wikito-converter-1.5.2.tgz#ae78bfdcdee86dc0137064d585543c59dbe47cc1"
|
||||||
|
integrity sha512-m7CcUUovtUCH5kwxmClxDnC0Q/QgepGr1SCZpyqIjshxTBdx1o7mKOmKboMQzdbXk/8DRLZ1Bbc1h3cKWx2IRw==
|
||||||
|
dependencies:
|
||||||
|
bluebird "^2.9.34"
|
||||||
|
bootstrap "^3.3.7"
|
||||||
|
commander "^2.8.1"
|
||||||
|
datauri "^1.0.5"
|
||||||
|
defaults "^1.0.2"
|
||||||
|
fs-extra "^0.23.1"
|
||||||
|
highlight.js "^9.12.0"
|
||||||
|
jquery "^2.1.4"
|
||||||
|
marked "^0.3.6"
|
||||||
|
node-dir "^0.1.9"
|
||||||
|
open "0.0.5"
|
||||||
|
winston "^1.0.1"
|
||||||
|
wkhtmltopdf "^0.1.5"
|
||||||
|
|
||||||
glob-parent@5.1.0:
|
glob-parent@5.1.0:
|
||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2"
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2"
|
||||||
|
@ -5372,7 +5440,7 @@ hex-color-regex@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
||||||
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
||||||
|
|
||||||
highlight.js@^9.6.0:
|
highlight.js@^9.12.0, highlight.js@^9.6.0:
|
||||||
version "9.18.3"
|
version "9.18.3"
|
||||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.3.tgz#a1a0a2028d5e3149e2380f8a865ee8516703d634"
|
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.3.tgz#a1a0a2028d5e3149e2380f8a865ee8516703d634"
|
||||||
integrity sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ==
|
integrity sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ==
|
||||||
|
@ -5650,6 +5718,11 @@ ignore@^5.1.1, ignore@^5.1.4, ignore@^5.1.8:
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
|
||||||
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
|
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
|
||||||
|
|
||||||
|
image-size@^0.6.2:
|
||||||
|
version "0.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.6.3.tgz#e7e5c65bb534bd7cdcedd6cb5166272a85f75fb2"
|
||||||
|
integrity sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==
|
||||||
|
|
||||||
import-cwd@^2.0.0:
|
import-cwd@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
|
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
|
||||||
|
@ -6188,7 +6261,7 @@ isobject@^3.0.0, isobject@^3.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||||
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
|
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
|
||||||
|
|
||||||
isstream@~0.1.2:
|
isstream@0.1.x, isstream@~0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||||
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
||||||
|
@ -6250,6 +6323,11 @@ jest-worker@^25.4.0:
|
||||||
merge-stream "^2.0.0"
|
merge-stream "^2.0.0"
|
||||||
supports-color "^7.0.0"
|
supports-color "^7.0.0"
|
||||||
|
|
||||||
|
jquery@^2.1.4:
|
||||||
|
version "2.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/jquery/-/jquery-2.2.4.tgz#2c89d6889b5eac522a7eea32c14521559c6cbf02"
|
||||||
|
integrity sha1-LInWiJterFIqfuoywUUhVZxsvwI=
|
||||||
|
|
||||||
js-beautify@^1.11.0, js-beautify@^1.6.12:
|
js-beautify@^1.11.0, js-beautify@^1.6.12:
|
||||||
version "1.11.0"
|
version "1.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.11.0.tgz#afb873dc47d58986360093dcb69951e8bcd5ded2"
|
resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.11.0.tgz#afb873dc47d58986360093dcb69951e8bcd5ded2"
|
||||||
|
@ -6395,6 +6473,13 @@ json5@^2.1.1, json5@^2.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "^1.2.5"
|
minimist "^1.2.5"
|
||||||
|
|
||||||
|
jsonfile@^2.1.0:
|
||||||
|
version "2.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
|
||||||
|
integrity sha1-NzaitCi4e72gzIO1P6PWM6NcKug=
|
||||||
|
optionalDependencies:
|
||||||
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
jsonfile@^4.0.0:
|
jsonfile@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||||
|
@ -6749,6 +6834,11 @@ markdown-table@^2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
repeat-string "^1.0.0"
|
repeat-string "^1.0.0"
|
||||||
|
|
||||||
|
marked@^0.3.6:
|
||||||
|
version "0.3.19"
|
||||||
|
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
|
||||||
|
integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==
|
||||||
|
|
||||||
mathml-tag-names@^2.1.3:
|
mathml-tag-names@^2.1.3:
|
||||||
version "2.1.3"
|
version "2.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
|
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
|
||||||
|
@ -6913,6 +7003,11 @@ mime@^2.3.1, mime@^2.4.4:
|
||||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
|
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
|
||||||
integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
|
integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
|
||||||
|
|
||||||
|
mimer@^0.3.2:
|
||||||
|
version "0.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/mimer/-/mimer-0.3.2.tgz#0b83aabdf48eaacfd2e093ed4c0ed3d38eda8073"
|
||||||
|
integrity sha512-N6NcgDQAevhP/02DQ/epK6daLy4NKrIHyTlJcO6qBiYn98q+Y4a/knNsAATCe1xLS2F0nEmJp+QYli2s8vKwyQ==
|
||||||
|
|
||||||
mimic-fn@^1.0.0:
|
mimic-fn@^1.0.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
|
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
|
||||||
|
@ -7222,6 +7317,13 @@ no-case@^3.0.3:
|
||||||
lower-case "^2.0.1"
|
lower-case "^2.0.1"
|
||||||
tslib "^1.10.0"
|
tslib "^1.10.0"
|
||||||
|
|
||||||
|
node-dir@^0.1.9:
|
||||||
|
version "0.1.17"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5"
|
||||||
|
integrity sha1-X1Zl2TNRM1yqvvjxxVRRbPXx5OU=
|
||||||
|
dependencies:
|
||||||
|
minimatch "^3.0.2"
|
||||||
|
|
||||||
node-environment-flags@1.0.6:
|
node-environment-flags@1.0.6:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088"
|
resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088"
|
||||||
|
@ -7496,6 +7598,11 @@ onetime@^5.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn "^2.1.0"
|
mimic-fn "^2.1.0"
|
||||||
|
|
||||||
|
open@0.0.5:
|
||||||
|
version "0.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/open/-/open-0.0.5.tgz#42c3e18ec95466b6bf0dc42f3a2945c3f0cad8fc"
|
||||||
|
integrity sha1-QsPhjslUZra/DcQvOilFw/DK2Pw=
|
||||||
|
|
||||||
open@^6.3.0:
|
open@^6.3.0:
|
||||||
version "6.4.0"
|
version "6.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9"
|
resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9"
|
||||||
|
@ -7954,6 +8061,11 @@ pkg-dir@^4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
find-up "^4.0.0"
|
find-up "^4.0.0"
|
||||||
|
|
||||||
|
pkginfo@0.3.x:
|
||||||
|
version "0.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21"
|
||||||
|
integrity sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=
|
||||||
|
|
||||||
pnp-webpack-plugin@^1.6.4:
|
pnp-webpack-plugin@^1.6.4:
|
||||||
version "1.6.4"
|
version "1.6.4"
|
||||||
resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149"
|
resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149"
|
||||||
|
@ -9185,6 +9297,11 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||||
|
|
||||||
|
semver@^7.3.2:
|
||||||
|
version "7.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
|
||||||
|
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
|
||||||
|
|
||||||
send@0.17.1:
|
send@0.17.1:
|
||||||
version "0.17.1"
|
version "0.17.1"
|
||||||
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
|
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
|
||||||
|
@ -9335,6 +9452,11 @@ simple-swizzle@^0.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish "^0.3.1"
|
is-arrayish "^0.3.1"
|
||||||
|
|
||||||
|
slang@>=0.2:
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/slang/-/slang-0.3.0.tgz#13af75b4f0c018c6a8193d704f65b23be4fbabdc"
|
||||||
|
integrity sha1-E691tPDAGMaoGT1wT2WyO+T7q9w=
|
||||||
|
|
||||||
slash@^1.0.0:
|
slash@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
|
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
|
||||||
|
@ -9601,6 +9723,11 @@ stable@^0.1.8:
|
||||||
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
|
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
|
||||||
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
|
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
|
||||||
|
|
||||||
|
stack-trace@0.0.x:
|
||||||
|
version "0.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
|
||||||
|
integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=
|
||||||
|
|
||||||
stack-utils@^1.0.1:
|
stack-utils@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
|
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
|
||||||
|
@ -10691,7 +10818,7 @@ vm-browserify@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||||
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
||||||
|
|
||||||
vue-apexcharts@^1.5.2:
|
vue-apexcharts@^1.6.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/vue-apexcharts/-/vue-apexcharts-1.6.0.tgz#c7d3afd93f712433d404e5ceeb4e3aa65f422af2"
|
resolved "https://registry.yarnpkg.com/vue-apexcharts/-/vue-apexcharts-1.6.0.tgz#c7d3afd93f712433d404e5ceeb4e3aa65f422af2"
|
||||||
integrity sha512-sT6tuVTLBwfH3TA7azecDNS/W70bmz14ZJI7aE7QIqcG9I6OywyH7x3hcOeY1v1DxttI8Svc5RuYj4Dd+A5F4g==
|
integrity sha512-sT6tuVTLBwfH3TA7azecDNS/W70bmz14ZJI7aE7QIqcG9I6OywyH7x3hcOeY1v1DxttI8Svc5RuYj4Dd+A5F4g==
|
||||||
|
@ -11201,6 +11328,26 @@ wide-align@1.1.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width "^1.0.2 || 2"
|
string-width "^1.0.2 || 2"
|
||||||
|
|
||||||
|
winston@^1.0.1:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/winston/-/winston-1.1.2.tgz#68edd769ff79d4f9528cf0e5d80021aade67480c"
|
||||||
|
integrity sha1-aO3Xaf951PlSjPDl2AAhqt5nSAw=
|
||||||
|
dependencies:
|
||||||
|
async "~1.0.0"
|
||||||
|
colors "1.0.x"
|
||||||
|
cycle "1.0.x"
|
||||||
|
eyes "0.1.x"
|
||||||
|
isstream "0.1.x"
|
||||||
|
pkginfo "0.3.x"
|
||||||
|
stack-trace "0.0.x"
|
||||||
|
|
||||||
|
wkhtmltopdf@^0.1.5:
|
||||||
|
version "0.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/wkhtmltopdf/-/wkhtmltopdf-0.1.6.tgz#e9db84eb10d4ee50a40f4c3a7f58ca3d5d365ec4"
|
||||||
|
integrity sha1-6duE6xDU7lCkD0w6f1jKPV02XsQ=
|
||||||
|
dependencies:
|
||||||
|
slang ">=0.2"
|
||||||
|
|
||||||
word-wrap@~1.2.3:
|
word-wrap@~1.2.3:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||||
|
|
7
go.mod
7
go.mod
|
@ -5,6 +5,7 @@ go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/GeertJohan/go.rice v1.0.0
|
github.com/GeertJohan/go.rice v1.0.0
|
||||||
|
github.com/aws/aws-sdk-go v1.30.20
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/fatih/structs v1.1.0
|
github.com/fatih/structs v1.1.0
|
||||||
github.com/foomo/simplecert v1.7.5
|
github.com/foomo/simplecert v1.7.5
|
||||||
|
@ -12,6 +13,8 @@ require (
|
||||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||||
github.com/getsentry/sentry-go v0.5.1
|
github.com/getsentry/sentry-go v0.5.1
|
||||||
github.com/go-mail/mail v2.3.1+incompatible
|
github.com/go-mail/mail v2.3.1+incompatible
|
||||||
|
github.com/golang/protobuf v1.4.0
|
||||||
|
github.com/gomarkdown/markdown v0.0.0-20200820230800-3724143f5294 // indirect
|
||||||
github.com/gorilla/mux v1.7.4
|
github.com/gorilla/mux v1.7.4
|
||||||
github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9
|
github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9
|
||||||
github.com/jinzhu/gorm v1.9.12
|
github.com/jinzhu/gorm v1.9.12
|
||||||
|
@ -19,6 +22,7 @@ require (
|
||||||
github.com/pelletier/go-toml v1.7.0 // indirect
|
github.com/pelletier/go-toml v1.7.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.1.0
|
github.com/prometheus/client_golang v1.1.0
|
||||||
|
github.com/shopspring/decimal v1.2.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.5.0
|
github.com/sirupsen/logrus v1.5.0
|
||||||
github.com/spf13/afero v1.2.2 // indirect
|
github.com/spf13/afero v1.2.2 // indirect
|
||||||
github.com/spf13/cast v1.3.1 // indirect
|
github.com/spf13/cast v1.3.1 // indirect
|
||||||
|
@ -28,6 +32,9 @@ require (
|
||||||
github.com/spf13/viper v1.6.3
|
github.com/spf13/viper v1.6.3
|
||||||
github.com/stretchr/testify v1.5.1
|
github.com/stretchr/testify v1.5.1
|
||||||
github.com/t-tiger/gorm-bulk-insert/v2 v2.0.1
|
github.com/t-tiger/gorm-bulk-insert/v2 v2.0.1
|
||||||
|
github.com/tdewolff/minify/v2 v2.8.0 // indirect
|
||||||
|
github.com/wellington/go-libsass v0.9.2
|
||||||
|
github.com/wellington/sass v0.0.0-20160911051022-cab90b3986d6
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||||
|
|
18
go.sum
18
go.sum
|
@ -114,6 +114,7 @@ github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
|
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
@ -253,6 +254,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
||||||
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/gomarkdown/markdown v0.0.0-20200820230800-3724143f5294 h1:rSb2ZQZ3B1rlWBWamxobyn0jTuGZHbPO5Rmjw48uWRM=
|
||||||
|
github.com/gomarkdown/markdown v0.0.0-20200820230800-3724143f5294/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
|
||||||
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
@ -397,6 +400,7 @@ github.com/liquidweb/liquidweb-go v1.6.1/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVL
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
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 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
@ -523,6 +527,8 @@ github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3
|
||||||
github.com/sacloud/libsacloud v1.36.1 h1:tCpFjWsvu/2Im8/SDmRZ49SttVXy7nHerobRc1LU9pI=
|
github.com/sacloud/libsacloud v1.36.1 h1:tCpFjWsvu/2Im8/SDmRZ49SttVXy7nHerobRc1LU9pI=
|
||||||
github.com/sacloud/libsacloud v1.36.1/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg=
|
github.com/sacloud/libsacloud v1.36.1/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
|
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||||
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
@ -574,6 +580,13 @@ 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/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
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 h1:HGVkRrwDCbmSP6h1CoBDj6l/mhnvsP5JbYaQ4ss0R6o=
|
||||||
github.com/t-tiger/gorm-bulk-insert/v2 v2.0.1/go.mod h1:I3xbaE9ud9/TEXzehwkHx86SyJwqeSNsX2X5oV61jIg=
|
github.com/t-tiger/gorm-bulk-insert/v2 v2.0.1/go.mod h1:I3xbaE9ud9/TEXzehwkHx86SyJwqeSNsX2X5oV61jIg=
|
||||||
|
github.com/tdewolff/minify v1.1.0 h1:nxHQi1ML+g3ZbZHffiZ6eC7vMqNvSRfX3KB5Y5y/kfw=
|
||||||
|
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
|
||||||
|
github.com/tdewolff/minify/v2 v2.8.0 h1:t3tOPWkTpKhsgxm3IM9Sy8hE2eIt30Oaa+2havJGGIE=
|
||||||
|
github.com/tdewolff/minify/v2 v2.8.0/go.mod h1:6zN8VLhMfFxNrwHROcboYNo2+huPNu4SV8DPh3PUQ8E=
|
||||||
|
github.com/tdewolff/parse/v2 v2.4.4 h1:uMdbQRtYbKR/msP9CbI7li9wK6pionYiH6s7ipltyGY=
|
||||||
|
github.com/tdewolff/parse/v2 v2.4.4/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||||
|
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||||
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7 h1:CpHxIaZzVy26GqJn8ptRyto8fuoYOd1v0fXm9bG3wQ8=
|
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7 h1:CpHxIaZzVy26GqJn8ptRyto8fuoYOd1v0fXm9bG3wQ8=
|
||||||
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
|
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
|
@ -597,6 +610,10 @@ github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV
|
||||||
github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA=
|
github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA=
|
||||||
github.com/vultr/govultr v0.3.3 h1:fVaF4h9u3VzTXxFsxvgBUCiM52EiahLqAPkizamLzYM=
|
github.com/vultr/govultr v0.3.3 h1:fVaF4h9u3VzTXxFsxvgBUCiM52EiahLqAPkizamLzYM=
|
||||||
github.com/vultr/govultr v0.3.3/go.mod h1:TUuUizMOFc7z+PNMssb6iGjKjQfpw5arIaOLfocVudQ=
|
github.com/vultr/govultr v0.3.3/go.mod h1:TUuUizMOFc7z+PNMssb6iGjKjQfpw5arIaOLfocVudQ=
|
||||||
|
github.com/wellington/go-libsass v0.9.2 h1:6Ims04UDdBs6/CGSVK5JC8FNikR5ssrsMMKE/uaO5Q8=
|
||||||
|
github.com/wellington/go-libsass v0.9.2/go.mod h1:mxgxgam0N0E+NAUMHLcu20Ccfc3mVpDkyrLDayqfiTs=
|
||||||
|
github.com/wellington/sass v0.0.0-20160911051022-cab90b3986d6 h1:qPS12y9iMXyKr2flmOG7RgiyUGkQxQibp1hx7uug9IQ=
|
||||||
|
github.com/wellington/sass v0.0.0-20160911051022-cab90b3986d6/go.mod h1:ncYBwTYUjmb7N+sZbf8WJYynLivoqFL+U2f8uOX2Yzk=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||||
|
@ -623,6 +640,7 @@ go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZ
|
||||||
go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw=
|
go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw=
|
||||||
go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
|
go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
|
||||||
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
|
|
@ -90,7 +90,7 @@ func apiCoreHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
app.UseCdn = null.NewNullBool(c.UseCdn.Bool)
|
app.UseCdn = null.NewNullBool(c.UseCdn.Bool)
|
||||||
app.AllowReports = null.NewNullBool(c.AllowReports.Bool)
|
app.AllowReports = null.NewNullBool(c.AllowReports.Bool)
|
||||||
utils.SentryInit(nil, app.AllowReports.Bool)
|
utils.SentryInit(app.AllowReports.Bool)
|
||||||
err = app.Update()
|
err = app.Update()
|
||||||
returnJson(core.App, w, r)
|
returnJson(core.App, w, r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ func init() {
|
||||||
utils.InitLogs()
|
utils.InitLogs()
|
||||||
source.Assets()
|
source.Assets()
|
||||||
dir = utils.Directory
|
dir = utils.Directory
|
||||||
core.New("test")
|
core.New("test", "testcommithere")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFailedHTTPServer(t *testing.T) {
|
func TestFailedHTTPServer(t *testing.T) {
|
||||||
|
@ -205,12 +205,6 @@ func TestMainApiRoutes(t *testing.T) {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "404 Error Page",
|
|
||||||
URL: "/api/missing_404_page",
|
|
||||||
Method: "GET",
|
|
||||||
ExpectedStatus: 404,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "Health Check endpoint",
|
Name: "Health Check endpoint",
|
||||||
URL: "/health",
|
URL: "/health",
|
||||||
|
|
|
@ -14,6 +14,10 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ExecuteResponse(w, r, "base.gohtml", core.App, nil)
|
ExecuteResponse(w, r, "base.gohtml", core.App, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func baseHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ExecuteResponse(w, r, "base.gohtml", core.App, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
health := map[string]interface{}{
|
health := map[string]interface{}{
|
||||||
"services": len(services.All()),
|
"services": len(services.All()),
|
||||||
|
@ -22,8 +26,3 @@ func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
returnJson(health, w, r)
|
returnJson(health, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
ExecuteResponse(w, r, "base.gohtml", core.App, nil)
|
|
||||||
}
|
|
||||||
|
|
|
@ -161,7 +161,7 @@ func TestApiNotifiersRoutes(t *testing.T) {
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Body: `{
|
Body: `{
|
||||||
"method": "slack",
|
"method": "slack",
|
||||||
"host": "https://slack.api/example/12345",
|
"host": "https://hooks.slack.com/services/TTJ1B49DP/XBNU09O9M/9uI2123SUnYBuGcxLopZomz9H",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"limits": 55
|
"limits": 55
|
||||||
}`,
|
}`,
|
||||||
|
@ -173,7 +173,7 @@ func TestApiNotifiersRoutes(t *testing.T) {
|
||||||
URL: "/api/notifier/slack",
|
URL: "/api/notifier/slack",
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
ExpectedStatus: 200,
|
ExpectedStatus: 200,
|
||||||
ExpectedContains: []string{`"method":"slack"`, `"host":"https://slack.api/example/12345"`},
|
ExpectedContains: []string{`"method":"slack"`, `"host":"https://hooks.slack.com/services/TTJ1B49DP/XBNU09O9M/9uI2123SUnYBuGcxLopZomz9H"`},
|
||||||
BeforeTest: SetTestENV,
|
BeforeTest: SetTestENV,
|
||||||
SecureRoute: true,
|
SecureRoute: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/statping/statping/utils"
|
"github.com/statping/statping/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/pprof"
|
"net/http/pprof"
|
||||||
|
"time"
|
||||||
|
|
||||||
_ "github.com/statping/statping/types/metrics"
|
_ "github.com/statping/statping/types/metrics"
|
||||||
)
|
)
|
||||||
|
@ -38,7 +39,7 @@ func Router() *mux.Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
bPath := utils.Params.GetString("BASE_PATH")
|
bPath := utils.Params.GetString("BASE_PATH")
|
||||||
sentryHandler := sentryhttp.New(sentryhttp.Options{})
|
sentryHandler := sentryhttp.New(sentryhttp.Options{Timeout: 5 * time.Second})
|
||||||
|
|
||||||
if bPath != "" {
|
if bPath != "" {
|
||||||
basePath = "/" + bPath + "/"
|
basePath = "/" + bPath + "/"
|
||||||
|
@ -180,7 +181,7 @@ func Router() *mux.Router {
|
||||||
// API Generic Routes
|
// API Generic Routes
|
||||||
r.Handle("/metrics", readOnly(promhttp.Handler(), false))
|
r.Handle("/metrics", readOnly(promhttp.Handler(), false))
|
||||||
r.Handle("/health", http.HandlerFunc(healthCheckHandler))
|
r.Handle("/health", http.HandlerFunc(healthCheckHandler))
|
||||||
r.NotFoundHandler = http.HandlerFunc(notFoundHandler)
|
r.NotFoundHandler = http.HandlerFunc(baseHandler)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,9 +94,9 @@ func startSSLServer(ip string) {
|
||||||
PreferServerCipherSuites: true,
|
PreferServerCipherSuites: true,
|
||||||
CipherSuites: []uint16{
|
CipherSuites: []uint16{
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||||
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
|
|
|
@ -101,7 +101,7 @@ func apiServicePatchHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !req.Online {
|
if !req.Online {
|
||||||
services.RecordFailure(service, issueDefault)
|
services.RecordFailure(service, issueDefault, "trigger")
|
||||||
} else {
|
} else {
|
||||||
services.RecordSuccess(service)
|
services.RecordSuccess(service)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
package notifiers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
"github.com/aws/aws-sdk-go/service/sns"
|
||||||
|
"github.com/statping/statping/types/null"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/statping/statping/types/failures"
|
||||||
|
"github.com/statping/statping/types/notifications"
|
||||||
|
"github.com/statping/statping/types/notifier"
|
||||||
|
"github.com/statping/statping/types/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ notifier.Notifier = (*amazonSNS)(nil)
|
||||||
|
|
||||||
|
type amazonSNS struct {
|
||||||
|
*notifications.Notification
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *amazonSNS) Select() *notifications.Notification {
|
||||||
|
return g.Notification
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *amazonSNS) Valid(values notifications.Values) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var AmazonSNS = &amazonSNS{¬ifications.Notification{
|
||||||
|
Method: "amazon_sns",
|
||||||
|
Title: "Amazon SNS",
|
||||||
|
Description: "Use amazonSNS to receive push notifications. Add your amazonSNS URL and App Token to receive notifications.",
|
||||||
|
Author: "Hunter Long",
|
||||||
|
AuthorUrl: "https://github.com/hunterlong",
|
||||||
|
Icon: "amazon",
|
||||||
|
Delay: 5 * time.Second,
|
||||||
|
Limits: 60,
|
||||||
|
SuccessData: null.NewNullString(`{{.Service.Name}} is back online and was down for {{.Service.Downtime.Human}}`),
|
||||||
|
FailureData: null.NewNullString(`{{.Service.Name}} is offline and has been down for {{.Service.Downtime.Human}}`),
|
||||||
|
DataType: "html",
|
||||||
|
Form: []notifications.NotificationForm{{
|
||||||
|
Type: "text",
|
||||||
|
Title: "AWS Access Token",
|
||||||
|
DbField: "api_key",
|
||||||
|
Placeholder: "AKPMED5XUXSEU3O5AB6M",
|
||||||
|
Required: true,
|
||||||
|
}, {
|
||||||
|
Type: "text",
|
||||||
|
Title: "AWS Secret Key",
|
||||||
|
DbField: "api_secret",
|
||||||
|
Placeholder: "39eAZODxEosHRgzLx173ttX9sCtJVOE8rzElRE9B",
|
||||||
|
Required: true,
|
||||||
|
}, {
|
||||||
|
Type: "text",
|
||||||
|
Title: "Region",
|
||||||
|
SmallText: "Amazon Region for SNS",
|
||||||
|
DbField: "var1",
|
||||||
|
Placeholder: "us-west-2",
|
||||||
|
Required: true,
|
||||||
|
}, {
|
||||||
|
Type: "text",
|
||||||
|
Title: "SNS Topic ARN",
|
||||||
|
SmallText: "The ARN of the Topic",
|
||||||
|
DbField: "host",
|
||||||
|
Placeholder: "arn:aws:sns:us-west-2:123456789012:YourTopic",
|
||||||
|
Required: true,
|
||||||
|
}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func valToAttr(val interface{}) *sns.MessageAttributeValue {
|
||||||
|
dataType := "String"
|
||||||
|
switch val.(type) {
|
||||||
|
case string, bool:
|
||||||
|
dataType = "String"
|
||||||
|
case int, int64, uint, uint64, uint32:
|
||||||
|
dataType = "Number"
|
||||||
|
}
|
||||||
|
return &sns.MessageAttributeValue{
|
||||||
|
DataType: aws.String(dataType),
|
||||||
|
StringValue: aws.String(fmt.Sprintf("%v", val)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func messageAttributesSNS(s services.Service, f failures.Failure) map[string]*sns.MessageAttributeValue {
|
||||||
|
attr := make(map[string]*sns.MessageAttributeValue)
|
||||||
|
attr["service_id"] = valToAttr(s.Id)
|
||||||
|
attr["online"] = valToAttr(s.Online)
|
||||||
|
attr["downtime_milliseconds"] = valToAttr(s.Downtime().Milliseconds())
|
||||||
|
if f.Id != 0 {
|
||||||
|
attr["failure_issue"] = valToAttr(f.Issue)
|
||||||
|
attr["failure_reason"] = valToAttr(f.Reason)
|
||||||
|
attr["failure_status_code"] = valToAttr(f.ErrorCode)
|
||||||
|
attr["failure_ping"] = valToAttr(f.PingTime)
|
||||||
|
}
|
||||||
|
return attr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send will send a HTTP Post to the amazonSNS API. It accepts type: string
|
||||||
|
func (g *amazonSNS) sendMessage(msg string, s services.Service, f failures.Failure) (string, error) {
|
||||||
|
creds := credentials.NewStaticCredentials(g.ApiKey.String, g.ApiSecret.String, "")
|
||||||
|
c := aws.NewConfig()
|
||||||
|
c.Credentials = creds
|
||||||
|
c.Region = aws.String(g.Var1.String)
|
||||||
|
sess, err := session.NewSession(c)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := sns.New(sess)
|
||||||
|
input := &sns.PublishInput{
|
||||||
|
Message: aws.String(msg),
|
||||||
|
TopicArn: aws.String(g.Host.String),
|
||||||
|
MessageAttributes: messageAttributesSNS(s, f),
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := client.Publish(input)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnFailure will trigger failing service
|
||||||
|
func (g *amazonSNS) OnFailure(s services.Service, f failures.Failure) (string, error) {
|
||||||
|
msg := ReplaceVars(g.FailureData.String, s, f)
|
||||||
|
return g.sendMessage(msg, s, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnSuccess will trigger successful service
|
||||||
|
func (g *amazonSNS) OnSuccess(s services.Service) (string, error) {
|
||||||
|
msg := ReplaceVars(g.SuccessData.String, s, failures.Failure{})
|
||||||
|
return g.sendMessage(msg, s, failures.Failure{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnTest will test the amazonSNS notifier
|
||||||
|
func (g *amazonSNS) OnTest() (string, error) {
|
||||||
|
s := services.Example(true)
|
||||||
|
f := failures.Example()
|
||||||
|
msg := ReplaceVars(`This is a test SNS notification from Statping. Service: {{.Service.Name}} - Downtime: {{.Service.Downtime.Human}}`, s, f)
|
||||||
|
return g.sendMessage(msg, s, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnSave will trigger when this notifier is saved
|
||||||
|
func (g *amazonSNS) OnSave() (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package notifiers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/statping/statping/database"
|
||||||
|
"github.com/statping/statping/types/core"
|
||||||
|
"github.com/statping/statping/types/failures"
|
||||||
|
"github.com/statping/statping/types/notifications"
|
||||||
|
"github.com/statping/statping/types/null"
|
||||||
|
"github.com/statping/statping/types/services"
|
||||||
|
"github.com/statping/statping/utils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAmazonSNSNotifier(t *testing.T) {
|
||||||
|
err := utils.InitLogs()
|
||||||
|
require.Nil(t, err)
|
||||||
|
snsToken := utils.Params.GetString("SNS_TOKEN")
|
||||||
|
snsSecret := utils.Params.GetString("SNS_SECRET")
|
||||||
|
snsRegion := utils.Params.GetString("SNS_REGION")
|
||||||
|
snsTopic := utils.Params.GetString("SNS_TOPIC")
|
||||||
|
|
||||||
|
db, err := database.OpenTester()
|
||||||
|
require.Nil(t, err)
|
||||||
|
db.AutoMigrate(¬ifications.Notification{})
|
||||||
|
notifications.SetDB(db)
|
||||||
|
core.Example()
|
||||||
|
|
||||||
|
if snsToken == "" || snsSecret == "" || snsRegion == "" || snsTopic == "" {
|
||||||
|
t.Log("SNS notifier testing skipped, missing SNS_TOKEN, SNS_SECRET, SNS_REGION, SNS_TOPIC environment variables")
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Load SNS", func(t *testing.T) {
|
||||||
|
AmazonSNS.ApiKey = null.NewNullString(snsToken)
|
||||||
|
AmazonSNS.ApiSecret = null.NewNullString(snsSecret)
|
||||||
|
AmazonSNS.Var1 = null.NewNullString(snsRegion)
|
||||||
|
AmazonSNS.Host = null.NewNullString(snsTopic)
|
||||||
|
AmazonSNS.Delay = 15 * time.Second
|
||||||
|
AmazonSNS.Enabled = null.NewNullBool(true)
|
||||||
|
|
||||||
|
Add(AmazonSNS)
|
||||||
|
|
||||||
|
assert.Equal(t, "Hunter Long", AmazonSNS.Author)
|
||||||
|
assert.Equal(t, snsToken, AmazonSNS.ApiKey.String)
|
||||||
|
assert.Equal(t, snsSecret, AmazonSNS.ApiSecret.String)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SNS Notifier Tester", func(t *testing.T) {
|
||||||
|
assert.True(t, AmazonSNS.CanSend())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SNS Notifier Tester OnSave", func(t *testing.T) {
|
||||||
|
_, err := AmazonSNS.OnSave()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SNS OnFailure", func(t *testing.T) {
|
||||||
|
_, err := AmazonSNS.OnFailure(services.Example(false), failures.Example())
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SNS OnSuccess", func(t *testing.T) {
|
||||||
|
_, err := AmazonSNS.OnSuccess(services.Example(true))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SNS Test", func(t *testing.T) {
|
||||||
|
_, err := AmazonSNS.OnTest()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
// DO NOT EDIT ** This file was generated with go generate on 2020-08-06 20:20:14.476432 +0000 UTC ** DO NOT EDIT //
|
// DO NOT EDIT ** This file was generated with go generate on 2020-08-21 21:37:06.638898 +0000 UTC ** DO NOT EDIT //
|
||||||
package notifiers
|
package notifiers
|
||||||
|
|
||||||
const emailSuccess = `<!doctype html><html xmlns=http://www.w3.org/1999/xhtml xmlns:v=urn:schemas-microsoft-com:vml xmlns:o=urn:schemas-microsoft-com:office:office><title>Statping Service Notification</title><meta http-equiv=x-ua-compatible content="IE=edge"><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><style type=text/css>
|
const emailSuccess = `<!doctype html><html xmlns=http://www.w3.org/1999/xhtml xmlns:v=urn:schemas-microsoft-com:vml xmlns:o=urn:schemas-microsoft-com:office:office><title>Statping Service Notification</title><meta http-equiv=x-ua-compatible content="IE=edge"><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><style type=text/css>
|
||||||
|
|
|
@ -79,7 +79,7 @@ func (m *mobilePush) OnFailure(s services.Service, f failures.Failure) (string,
|
||||||
func (m *mobilePush) OnSuccess(s services.Service) (string, error) {
|
func (m *mobilePush) OnSuccess(s services.Service) (string, error) {
|
||||||
data := dataJson(s, failures.Failure{})
|
data := dataJson(s, failures.Failure{})
|
||||||
msg := &pushArray{
|
msg := &pushArray{
|
||||||
Message: fmt.Sprintf("%s is currently online!", s.Name),
|
Message: fmt.Sprintf("%s is back online and was down for %s", s.Name, s.Downtime().Human()),
|
||||||
Title: "Service Online",
|
Title: "Service Online",
|
||||||
Data: data,
|
Data: data,
|
||||||
Platform: 2,
|
Platform: 2,
|
||||||
|
|
|
@ -19,7 +19,6 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMobileNotifier(t *testing.T) {
|
func TestMobileNotifier(t *testing.T) {
|
||||||
t.SkipNow()
|
|
||||||
err := utils.InitLogs()
|
err := utils.InitLogs()
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
@ -48,7 +47,7 @@ func TestMobileNotifier(t *testing.T) {
|
||||||
Add(Mobile)
|
Add(Mobile)
|
||||||
|
|
||||||
assert.Equal(t, "Hunter Long", Mobile.Author)
|
assert.Equal(t, "Hunter Long", Mobile.Author)
|
||||||
assert.Equal(t, mobileToken, Mobile.Var1)
|
assert.Equal(t, mobileToken, Mobile.Var1.String)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Mobile Notifier Tester", func(t *testing.T) {
|
t.Run("Mobile Notifier Tester", func(t *testing.T) {
|
||||||
|
|
|
@ -35,6 +35,7 @@ func InitNotifiers() {
|
||||||
Pushover,
|
Pushover,
|
||||||
statpingMailer,
|
statpingMailer,
|
||||||
Gotify,
|
Gotify,
|
||||||
|
AmazonSNS,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/statping/statping/types/null"
|
"github.com/statping/statping/types/null"
|
||||||
"github.com/statping/statping/types/services"
|
"github.com/statping/statping/types/services"
|
||||||
"github.com/statping/statping/utils"
|
"github.com/statping/statping/utils"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -93,5 +94,10 @@ func (s *slack) OnSave() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *slack) Valid(values notifications.Values) error {
|
func (s *slack) Valid(values notifications.Values) error {
|
||||||
|
regex := `https\:\/\/hooks\.slack\.com/services/[A-Z0-9]{7,11}/[A-Z0-9]{7,11}/[a-zA-Z0-9]{20,22}`
|
||||||
|
r := regexp.MustCompile(regex)
|
||||||
|
if !r.MatchString(values.Host) {
|
||||||
|
return errors.New("slack webhook does not match with expected regex " + regex)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,7 @@ func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) {
|
||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", "Statping")
|
req.Header.Set("User-Agent", "Statping")
|
||||||
req.Header.Set("Statping-Version", utils.Version)
|
req.Header.Set("Statping-Version", utils.Params.GetString("VERSION"))
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gomarkdown/markdown"
|
||||||
|
"github.com/gomarkdown/markdown/html"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const wikiUrl = "https://github.com/statping/statping.wiki"
|
||||||
|
|
||||||
|
var vue = `<template>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="row mb-4">
|
||||||
|
{{ range .Categories }}
|
||||||
|
<div class="col">
|
||||||
|
<h4 class="h4 mb-2">{{ .String }}</h4>
|
||||||
|
{{ range .Pages }}
|
||||||
|
<a @click.prevent='tab="{{.String}}"' class="d-block mb-1 text-link" href="#">{{.String}}</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12" v-if='tab === "Home"'>
|
||||||
|
<div v-pre>
|
||||||
|
{{html .Home.Data}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ range .Pages }}
|
||||||
|
<div class="col-12" v-if='tab === "{{.String}}"'>
|
||||||
|
<h1 class="h1 mt-5 mb-5 text-muted">{{ .String }}</h1>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
<div v-pre>
|
||||||
|
{{html .Data}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div class="col-12 shadow-md mt-5">
|
||||||
|
<div class="text-dim" v-pre>
|
||||||
|
{{html .Footer.Data}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center small text-dim" v-pre>
|
||||||
|
Automatically generated from Statping's Wiki on {{.CreatedAt}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Help',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
tab: "Home",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
IMG {
|
||||||
|
max-width: 80%;
|
||||||
|
alignment: center;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`
|
||||||
|
|
||||||
|
var temp *template.Template
|
||||||
|
|
||||||
|
type Category struct {
|
||||||
|
String string
|
||||||
|
Pages []*Page
|
||||||
|
}
|
||||||
|
|
||||||
|
type Page struct {
|
||||||
|
String string
|
||||||
|
Data string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Render struct {
|
||||||
|
Categories []*Category
|
||||||
|
Pages []*Page
|
||||||
|
Home *Page
|
||||||
|
Footer *Page
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("RUNNING: ./source/generate_help.go")
|
||||||
|
fmt.Println("\n\nGenerating Help.vue from Statping's Wiki")
|
||||||
|
fmt.Println("Cloning ", wikiUrl)
|
||||||
|
cmd := exec.Command("git", "clone", wikiUrl)
|
||||||
|
cmd.Start()
|
||||||
|
cmd.Wait()
|
||||||
|
|
||||||
|
fmt.Println("Generating Help view from Wiki")
|
||||||
|
|
||||||
|
d, _ := ioutil.ReadFile("statping.wiki/_Sidebar.md")
|
||||||
|
|
||||||
|
var cats []*Category
|
||||||
|
var pages []*Page
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(string(d)))
|
||||||
|
var thisCategory *Category
|
||||||
|
for scanner.Scan() {
|
||||||
|
txt := scanner.Text()
|
||||||
|
if txt == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if txt[0:1] == "#" {
|
||||||
|
newCate := &Category{
|
||||||
|
String: txt[2:len(txt)],
|
||||||
|
}
|
||||||
|
if txt[2:len(txt)] == "Contact" || txt[2:len(txt)] == "Badges" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
thisCategory = newCate
|
||||||
|
cats = append(cats, newCate)
|
||||||
|
}
|
||||||
|
if txt[0:2] == "[[" {
|
||||||
|
file := "statping.wiki/" + txt[2:len(txt)-2] + ".md"
|
||||||
|
file = strings.ReplaceAll(file, " ", "-")
|
||||||
|
page := &Page{
|
||||||
|
String: txt[2 : len(txt)-2],
|
||||||
|
Data: open(file),
|
||||||
|
}
|
||||||
|
pages = append(pages, page)
|
||||||
|
thisCategory.Pages = append(thisCategory.Pages, page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
home := &Page{
|
||||||
|
String: "Home",
|
||||||
|
Data: open("statping.wiki/Home.md"),
|
||||||
|
}
|
||||||
|
|
||||||
|
footer := &Page{
|
||||||
|
String: "Footer",
|
||||||
|
Data: open("statping.wiki/_Footer.md"),
|
||||||
|
}
|
||||||
|
|
||||||
|
w := bytes.NewBufferString("")
|
||||||
|
temp = template.New("wiki")
|
||||||
|
temp.Funcs(template.FuncMap{
|
||||||
|
"html": func(val string) template.HTML {
|
||||||
|
return template.HTML(val)
|
||||||
|
},
|
||||||
|
"fake": func(val string) template.HTML {
|
||||||
|
return template.HTML(`{{` + val + `}}`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
temp, _ = temp.Parse(vue)
|
||||||
|
temp.ExecuteTemplate(w, "wiki", Render{Categories: cats, Pages: pages, Home: home, Footer: footer, CreatedAt: time.Now().UTC()})
|
||||||
|
|
||||||
|
fmt.Println("Saving wiki page to: ./frontend/src/pages/Home.vue")
|
||||||
|
ioutil.WriteFile("../frontend/src/pages/Help.vue", w.Bytes(), os.FileMode(0755))
|
||||||
|
|
||||||
|
fmt.Println("Deleting statping wiki repo")
|
||||||
|
os.RemoveAll("statping.wiki")
|
||||||
|
}
|
||||||
|
|
||||||
|
func open(filename string) string {
|
||||||
|
htmlFlags := html.CommonFlags | html.HrefTargetBlank
|
||||||
|
opts := html.RendererOptions{Flags: htmlFlags}
|
||||||
|
renderer := html.NewRenderer(opts)
|
||||||
|
|
||||||
|
d, _ := ioutil.ReadFile(filename)
|
||||||
|
output := markdown.ToHTML(d, nil, renderer)
|
||||||
|
return string(output)
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const replace = `this\.version = "[0-9]\.[0-9]{2}\.[0-9]{2}";`
|
||||||
|
const replaceCommit = `this\.commit = \"[a-z0-9]{40}\"\;`
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("RUNNING: ./source/generate_version.go")
|
||||||
|
version, _ := ioutil.ReadFile("../version.txt")
|
||||||
|
apiJsFile, _ := ioutil.ReadFile("../frontend/src/API.js")
|
||||||
|
|
||||||
|
w := bytes.NewBuffer(nil)
|
||||||
|
cmd := exec.Command("git", "rev-parse", "HEAD")
|
||||||
|
cmd.Stdout = w
|
||||||
|
cmd.Run()
|
||||||
|
gitCommit := strings.TrimSpace(w.String())
|
||||||
|
|
||||||
|
fmt.Println("git commit: ", gitCommit)
|
||||||
|
|
||||||
|
replaceWith := `this.version = "` + strings.TrimSpace(string(version)) + `";`
|
||||||
|
replaceCommitWith := `this.commit = "` + gitCommit + `";`
|
||||||
|
|
||||||
|
vRex := regexp.MustCompile(replace)
|
||||||
|
newApiFile := vRex.ReplaceAllString(string(apiJsFile), replaceWith)
|
||||||
|
cRex := regexp.MustCompile(replaceCommit)
|
||||||
|
newApiFile = cRex.ReplaceAllString(newApiFile, replaceCommitWith)
|
||||||
|
|
||||||
|
fmt.Printf("Setting version %s to frontend/src/API.js\n", string(version))
|
||||||
|
ioutil.WriteFile("../frontend/src/API.js", []byte(newApiFile), os.FileMode(0755))
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
package source
|
package source
|
||||||
|
|
||||||
|
//go:generate go run generate_help.go
|
||||||
|
//go:generate go run generate_version.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/GeertJohan/go.rice"
|
"github.com/GeertJohan/go.rice"
|
||||||
|
|
|
@ -21,11 +21,6 @@ import (
|
||||||
"github.com/statping/statping/types/users"
|
"github.com/statping/statping/types/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
Version string
|
|
||||||
Commit string
|
|
||||||
)
|
|
||||||
|
|
||||||
func (d *DbConfig) ResetCore() error {
|
func (d *DbConfig) ResetCore() error {
|
||||||
if d.Db.HasTable("core") {
|
if d.Db.HasTable("core") {
|
||||||
return nil
|
return nil
|
||||||
|
@ -126,7 +121,7 @@ func (d *DbConfig) MigrateDatabase() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Migrating App to version: %s (%s)", Version, Commit)
|
log.Infof("Migrating App to version: %s (%s)", utils.Params.GetString("VERSION"), utils.Params.GetString("COMMIT"))
|
||||||
if err := tx.Table("core").AutoMigrate(&core.Core{}); err.Error() != nil {
|
if err := tx.Table("core").AutoMigrate(&core.Core{}); err.Error() != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error()))
|
log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error()))
|
||||||
|
@ -137,7 +132,7 @@ func (d *DbConfig) MigrateDatabase() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Db.Table("core").Model(&core.Core{}).Update("version", Version)
|
d.Db.Table("core").Model(&core.Core{}).Update("version", utils.Params.GetString("VERSION"))
|
||||||
|
|
||||||
log.Infoln("Statping Database Tables Migrated")
|
log.Infoln("Statping Database Tables Migrated")
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,8 @@ func Select() (*Core, error) {
|
||||||
if utils.Params.GetString("API_SECRET") != "" {
|
if utils.Params.GetString("API_SECRET") != "" {
|
||||||
App.ApiSecret = utils.Params.GetString("API_SECRET")
|
App.ApiSecret = utils.Params.GetString("API_SECRET")
|
||||||
}
|
}
|
||||||
|
App.Version = utils.Params.GetString("VERSION")
|
||||||
|
App.Commit = utils.Params.GetString("COMMIT")
|
||||||
return App, q.Error()
|
return App, q.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,8 @@ func Samples() error {
|
||||||
MigrationId: utils.Now().Unix(),
|
MigrationId: utils.Now().Unix(),
|
||||||
Language: utils.Params.GetString("LANGUAGE"),
|
Language: utils.Params.GetString("LANGUAGE"),
|
||||||
OAuth: oauth,
|
OAuth: oauth,
|
||||||
Version: utils.Version,
|
Version: utils.Params.GetString("VERSION"),
|
||||||
|
Commit: utils.Params.GetString("COMMIT"),
|
||||||
}
|
}
|
||||||
|
|
||||||
return core.Create()
|
return core.Create()
|
||||||
|
|
|
@ -10,9 +10,10 @@ var (
|
||||||
App *Core
|
App *Core
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(version string) {
|
func New(version, commit string) {
|
||||||
App = new(Core)
|
App = new(Core)
|
||||||
App.Version = version
|
App.Version = version
|
||||||
|
App.Commit = commit
|
||||||
App.Started = utils.Now()
|
App.Started = utils.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ type Core struct {
|
||||||
Footer null.NullString `gorm:"column:footer" json:"footer"`
|
Footer null.NullString `gorm:"column:footer" json:"footer"`
|
||||||
Domain string `gorm:"not null;column:domain" json:"domain"`
|
Domain string `gorm:"not null;column:domain" json:"domain"`
|
||||||
Version string `gorm:"column:version" json:"version"`
|
Version string `gorm:"column:version" json:"version"`
|
||||||
|
Commit string `gorm:"-" json:"commit"`
|
||||||
Language string `gorm:"column:language" json:"language"`
|
Language string `gorm:"column:language" json:"language"`
|
||||||
Setup bool `gorm:"-" json:"setup"`
|
Setup bool `gorm:"-" json:"setup"`
|
||||||
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
|
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
|
||||||
|
|
|
@ -22,6 +22,7 @@ func Example() Failure {
|
||||||
Service: 1,
|
Service: 1,
|
||||||
Checkin: 0,
|
Checkin: 0,
|
||||||
PingTime: 48309,
|
PingTime: 48309,
|
||||||
|
Reason: "status_code",
|
||||||
CreatedAt: utils.Now(),
|
CreatedAt: utils.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +35,7 @@ func Samples() error {
|
||||||
f1 := &Failure{
|
f1 := &Failure{
|
||||||
Service: i,
|
Service: i,
|
||||||
Issue: "Server failure",
|
Issue: "Server failure",
|
||||||
|
Reason: "lookup",
|
||||||
CreatedAt: utils.Now().Add(-time.Duration(3*i) * 86400),
|
CreatedAt: utils.Now().Add(-time.Duration(3*i) * 86400),
|
||||||
}
|
}
|
||||||
if err := f1.Create(); err != nil {
|
if err := f1.Create(); err != nil {
|
||||||
|
@ -42,7 +44,8 @@ func Samples() error {
|
||||||
|
|
||||||
f2 := &Failure{
|
f2 := &Failure{
|
||||||
Service: i,
|
Service: i,
|
||||||
Issue: "Server failure",
|
Issue: "Regex failed to match the response",
|
||||||
|
Reason: "regex",
|
||||||
CreatedAt: utils.Now().Add(-time.Duration(5*i) * 12400),
|
CreatedAt: utils.Now().Add(-time.Duration(5*i) * 12400),
|
||||||
}
|
}
|
||||||
if err := f2.Create(); err != nil {
|
if err := f2.Create(); err != nil {
|
||||||
|
|
|
@ -2,11 +2,6 @@ package failures
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
const (
|
|
||||||
limitedFailures = 32
|
|
||||||
limitedHits = 32
|
|
||||||
)
|
|
||||||
|
|
||||||
// Failure is a failed attempt to check a service. Any a service does not meet the expected requirements,
|
// Failure is a failed attempt to check a service. Any a service does not meet the expected requirements,
|
||||||
// a new Failure will be inserted into Db.
|
// a new Failure will be inserted into Db.
|
||||||
type Failure struct {
|
type Failure struct {
|
||||||
|
@ -18,6 +13,7 @@ type Failure struct {
|
||||||
Service int64 `gorm:"index;column:service" json:"-"`
|
Service int64 `gorm:"index;column:service" json:"-"`
|
||||||
Checkin int64 `gorm:"index;column:checkin" json:"-"`
|
Checkin int64 `gorm:"index;column:checkin" json:"-"`
|
||||||
PingTime int64 `gorm:"column:ping_time" json:"ping"`
|
PingTime int64 `gorm:"column:ping_time" json:"ping"`
|
||||||
|
Reason string `gorm:"column:reason" json:"reason,omitempty"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const limitFailures = 32
|
||||||
|
|
||||||
func (s *Service) FailuresColumnID() (string, int64) {
|
func (s *Service) FailuresColumnID() (string, int64) {
|
||||||
return "service", s.Id
|
return "service", s.Id
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,11 @@ import (
|
||||||
"github.com/statping/statping/utils"
|
"github.com/statping/statping/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func AddNotifier(n ServiceNotifier) {
|
||||||
|
notif := n.Select()
|
||||||
|
allNotifiers[notif.Method] = n
|
||||||
|
}
|
||||||
|
|
||||||
func sendSuccess(s *Service) {
|
func sendSuccess(s *Service) {
|
||||||
if !s.AllowNotifications.Bool {
|
if !s.AllowNotifications.Bool {
|
||||||
return
|
return
|
||||||
|
|
|
@ -94,7 +94,7 @@ func CheckIcmp(s *Service, record bool) (*Service, error) {
|
||||||
dur, err := utils.Ping(s.Domain, s.Timeout)
|
dur, err := utils.Ping(s.Domain, s.Timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if record {
|
if record {
|
||||||
RecordFailure(s, fmt.Sprintf("Could not send ICMP to service %v, %v", s.Domain, err))
|
RecordFailure(s, fmt.Sprintf("Could not send ICMP to service %v, %v", s.Domain, err), "lookup")
|
||||||
}
|
}
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ func CheckGrpc(s *Service, record bool) (*Service, error) {
|
||||||
dnsLookup, err := dnsCheck(s)
|
dnsLookup, err := dnsCheck(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if record {
|
if record {
|
||||||
RecordFailure(s, fmt.Sprintf("Could not get IP address for GRPC service %v, %v", s.Domain, err))
|
RecordFailure(s, fmt.Sprintf("Could not get IP address for GRPC service %v, %v", s.Domain, err), "lookup")
|
||||||
}
|
}
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
@ -137,13 +137,13 @@ func CheckGrpc(s *Service, record bool) (*Service, error) {
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if record {
|
if record {
|
||||||
RecordFailure(s, fmt.Sprintf("Dial Error %v", err))
|
RecordFailure(s, fmt.Sprintf("Dial Error %v", err), "connection")
|
||||||
}
|
}
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
if err := conn.Close(); err != nil {
|
if err := conn.Close(); err != nil {
|
||||||
if record {
|
if record {
|
||||||
RecordFailure(s, fmt.Sprintf("%v Socket Close Error %v", strings.ToUpper(s.Type), err))
|
RecordFailure(s, fmt.Sprintf("%v Socket Close Error %v", strings.ToUpper(s.Type), err), "close")
|
||||||
}
|
}
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,7 @@ func CheckTcp(s *Service, record bool) (*Service, error) {
|
||||||
dnsLookup, err := dnsCheck(s)
|
dnsLookup, err := dnsCheck(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if record {
|
if record {
|
||||||
RecordFailure(s, fmt.Sprintf("Could not get IP address for TCP service %v, %v", s.Domain, err))
|
RecordFailure(s, fmt.Sprintf("Could not get IP address for TCP service %v, %v", s.Domain, err), "lookup")
|
||||||
}
|
}
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
@ -189,7 +189,7 @@ func CheckTcp(s *Service, record bool) (*Service, error) {
|
||||||
conn, err := net.DialTimeout(s.Type, domain, time.Duration(s.Timeout)*time.Second)
|
conn, err := net.DialTimeout(s.Type, domain, time.Duration(s.Timeout)*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if record {
|
if record {
|
||||||
RecordFailure(s, fmt.Sprintf("Dial Error: %v", err))
|
RecordFailure(s, fmt.Sprintf("Dial Error: %v", err), "tls")
|
||||||
}
|
}
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
@ -203,7 +203,7 @@ func CheckTcp(s *Service, record bool) (*Service, error) {
|
||||||
conn, err := tls.DialWithDialer(dialer, s.Type, domain, tlsConfig)
|
conn, err := tls.DialWithDialer(dialer, s.Type, domain, tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if record {
|
if record {
|
||||||
RecordFailure(s, fmt.Sprintf("Dial Error: %v", err))
|
RecordFailure(s, fmt.Sprintf("Dial Error: %v", err), "tls")
|
||||||
}
|
}
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
@ -232,7 +232,7 @@ func CheckHttp(s *Service, record bool) (*Service, error) {
|
||||||
dnsLookup, err := dnsCheck(s)
|
dnsLookup, err := dnsCheck(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if record {
|
if record {
|
||||||
RecordFailure(s, fmt.Sprintf("Could not get IP address for domain %v, %v", s.Domain, err))
|
RecordFailure(s, fmt.Sprintf("Could not get IP address for domain %v, %v", s.Domain, err), "lookup")
|
||||||
}
|
}
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
@ -284,7 +284,7 @@ func CheckHttp(s *Service, record bool) (*Service, error) {
|
||||||
content, res, err = utils.HttpRequest(s.Domain, s.Method, contentType, headers, data, timeout, s.VerifySSL.Bool, customTLS)
|
content, res, err = utils.HttpRequest(s.Domain, s.Method, contentType, headers, data, timeout, s.VerifySSL.Bool, customTLS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if record {
|
if record {
|
||||||
RecordFailure(s, fmt.Sprintf("HTTP Error %v", err))
|
RecordFailure(s, fmt.Sprintf("HTTP Error %v", err), "request")
|
||||||
}
|
}
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
@ -301,14 +301,14 @@ func CheckHttp(s *Service, record bool) (*Service, error) {
|
||||||
}
|
}
|
||||||
if !match {
|
if !match {
|
||||||
if record {
|
if record {
|
||||||
RecordFailure(s, fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected))
|
RecordFailure(s, fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected), "regex")
|
||||||
}
|
}
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.ExpectedStatus != res.StatusCode {
|
if s.ExpectedStatus != res.StatusCode {
|
||||||
if record {
|
if record {
|
||||||
RecordFailure(s, fmt.Sprintf("HTTP Status Code %v did not match %v", res.StatusCode, s.ExpectedStatus))
|
RecordFailure(s, fmt.Sprintf("HTTP Status Code %v did not match %v", res.StatusCode, s.ExpectedStatus), "status_code")
|
||||||
}
|
}
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
@ -341,13 +341,8 @@ func RecordSuccess(s *Service) {
|
||||||
sendSuccess(s)
|
sendSuccess(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddNotifier(n ServiceNotifier) {
|
|
||||||
notif := n.Select()
|
|
||||||
allNotifiers[notif.Method] = n
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordFailure will create a new 'Failure' record in the database for a offline service
|
// RecordFailure will create a new 'Failure' record in the database for a offline service
|
||||||
func RecordFailure(s *Service, issue string) {
|
func RecordFailure(s *Service, issue, reason string) {
|
||||||
s.LastOffline = utils.Now()
|
s.LastOffline = utils.Now()
|
||||||
|
|
||||||
fail := &failures.Failure{
|
fail := &failures.Failure{
|
||||||
|
@ -356,6 +351,7 @@ func RecordFailure(s *Service, issue string) {
|
||||||
PingTime: s.PingTime,
|
PingTime: s.PingTime,
|
||||||
CreatedAt: utils.Now(),
|
CreatedAt: utils.Now(),
|
||||||
ErrorCode: s.LastStatusCode,
|
ErrorCode: s.LastStatusCode,
|
||||||
|
Reason: reason,
|
||||||
}
|
}
|
||||||
log.WithFields(utils.ToFields(fail, s)).
|
log.WithFields(utils.ToFields(fail, s)).
|
||||||
Warnln(fmt.Sprintf("Service %v Failing: %v | Lookup in: %v", s.Name, issue, humanMicro(fail.PingTime)))
|
Warnln(fmt.Sprintf("Service %v Failing: %v | Lookup in: %v", s.Name, issue, humanMicro(fail.PingTime)))
|
||||||
|
@ -365,6 +361,14 @@ func RecordFailure(s *Service, issue string) {
|
||||||
}
|
}
|
||||||
s.Online = false
|
s.Online = false
|
||||||
s.DownText = s.DowntimeText()
|
s.DownText = s.DowntimeText()
|
||||||
|
|
||||||
|
limitOffset := len(s.Failures)
|
||||||
|
if len(s.Failures) >= limitFailures {
|
||||||
|
limitOffset = limitFailures - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Failures = append([]*failures.Failure{fail}, s.Failures[:limitOffset]...)
|
||||||
|
|
||||||
metrics.Gauge("online", 0., s.Name, s.Type)
|
metrics.Gauge("online", 0., s.Name, s.Type)
|
||||||
metrics.Inc("failure", s.Name)
|
metrics.Inc("failure", s.Name)
|
||||||
sendFailure(s, fail)
|
sendFailure(s, fail)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package services
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/statping/statping/database"
|
"github.com/statping/statping/database"
|
||||||
"github.com/statping/statping/types/checkins"
|
"github.com/statping/statping/types/checkins"
|
||||||
|
@ -470,7 +469,7 @@ func TestServices(t *testing.T) {
|
||||||
item, err := Find(1)
|
item, err := Find(1)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
amount := item.Downtime().Seconds()
|
amount := item.Downtime().Seconds()
|
||||||
assert.Equal(t, "75", fmt.Sprintf("%0.f", amount))
|
assert.GreaterOrEqual(t, int64(75), int64(amount))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test Failures Since", func(t *testing.T) {
|
t.Run("Test Failures Since", func(t *testing.T) {
|
||||||
|
|
|
@ -269,7 +269,7 @@ func runNotifyTests(t *testing.T, notif *exampleNotifier, tests ...notifyTest) {
|
||||||
if test.OnSuccess {
|
if test.OnSuccess {
|
||||||
RecordSuccess(test.Service)
|
RecordSuccess(test.Service)
|
||||||
} else {
|
} else {
|
||||||
RecordFailure(test.Service, "test issue")
|
RecordFailure(test.Service, "test issue", "lookup")
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, test.ExpectedSuccess, notif.success)
|
assert.Equal(t, test.ExpectedSuccess, notif.success)
|
||||||
|
|
|
@ -24,11 +24,11 @@ func InitEnvs() {
|
||||||
Log.Errorln(err)
|
Log.Errorln(err)
|
||||||
defaultDir = "."
|
defaultDir = "."
|
||||||
}
|
}
|
||||||
Params.Set("VERSION", Version)
|
|
||||||
Params.SetDefault("DISABLE_HTTP", false)
|
Params.SetDefault("DISABLE_HTTP", false)
|
||||||
Params.SetDefault("STATPING_DIR", defaultDir)
|
Params.SetDefault("STATPING_DIR", defaultDir)
|
||||||
Params.SetDefault("GO_ENV", "production")
|
Params.SetDefault("GO_ENV", "production")
|
||||||
Params.SetDefault("DEBUG", false)
|
Params.SetDefault("DEBUG", false)
|
||||||
|
Params.SetDefault("DEMO_MODE", false)
|
||||||
Params.SetDefault("DB_CONN", "")
|
Params.SetDefault("DB_CONN", "")
|
||||||
Params.SetDefault("DISABLE_LOGS", false)
|
Params.SetDefault("DISABLE_LOGS", false)
|
||||||
Params.SetDefault("USE_ASSETS", false)
|
Params.SetDefault("USE_ASSETS", false)
|
||||||
|
|
13
utils/log.go
13
utils/log.go
|
@ -21,7 +21,6 @@ var (
|
||||||
LastLines []*logRow
|
LastLines []*logRow
|
||||||
LockLines sync.Mutex
|
LockLines sync.Mutex
|
||||||
VerboseMode int
|
VerboseMode int
|
||||||
Version string
|
|
||||||
allowReports bool
|
allowReports bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,21 +29,15 @@ const (
|
||||||
errorReporter = "https://ddf2784201134d51a20c3440e222cebe@sentry.statping.com/4"
|
errorReporter = "https://ddf2784201134d51a20c3440e222cebe@sentry.statping.com/4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SentryInit(v *string, allow bool) {
|
func SentryInit(allow bool) {
|
||||||
allowReports = allow
|
allowReports = allow
|
||||||
if v != nil {
|
|
||||||
if *v == "" {
|
|
||||||
*v = "development"
|
|
||||||
}
|
|
||||||
Version = *v
|
|
||||||
}
|
|
||||||
goEnv := Params.GetString("GO_ENV")
|
goEnv := Params.GetString("GO_ENV")
|
||||||
allowReports := Params.GetBool("ALLOW_REPORTS")
|
allowReports := Params.GetBool("ALLOW_REPORTS")
|
||||||
if allow || goEnv == "test" || allowReports {
|
if allow || goEnv == "test" || allowReports {
|
||||||
if err := sentry.Init(sentry.ClientOptions{
|
if err := sentry.Init(sentry.ClientOptions{
|
||||||
Dsn: errorReporter,
|
Dsn: errorReporter,
|
||||||
Environment: goEnv,
|
Environment: goEnv,
|
||||||
Release: Version,
|
Release: Params.GetString("VERSION"),
|
||||||
AttachStacktrace: true,
|
AttachStacktrace: true,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
Log.Errorln(err)
|
Log.Errorln(err)
|
||||||
|
@ -70,7 +63,7 @@ func SentryLogEntry(entry *Logger.Entry) {
|
||||||
e := sentry.NewEvent()
|
e := sentry.NewEvent()
|
||||||
e.Message = entry.Message
|
e.Message = entry.Message
|
||||||
e.Tags = sentryTags()
|
e.Tags = sentryTags()
|
||||||
e.Release = Version
|
e.Release = Params.GetString("VERSION")
|
||||||
e.Contexts = entry.Data
|
e.Contexts = entry.Data
|
||||||
sentry.CaptureEvent(e)
|
sentry.CaptureEvent(e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,7 +174,7 @@ func HttpRequest(endpoint, method string, contentType interface{}, headers []str
|
||||||
}
|
}
|
||||||
// set default headers so end user can overwrite them if needed
|
// set default headers so end user can overwrite them if needed
|
||||||
req.Header.Set("User-Agent", "Statping")
|
req.Header.Set("User-Agent", "Statping")
|
||||||
req.Header.Set("Statping-Version", Version)
|
req.Header.Set("Statping-Version", Params.GetString("VERSION"))
|
||||||
if contentType != nil {
|
if contentType != nil {
|
||||||
req.Header.Set("Content-Type", contentType.(string))
|
req.Header.Set("Content-Type", contentType.(string))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
0.90.63
|
0.90.64
|
||||||
|
|
Loading…
Reference in New Issue