Merge pull request #554 from statping/fixes-features-1

Fixes n Features 1
revert-560-notifiers-update-plus v0.90.34
Hunter Long 2020-05-01 04:56:52 -07:00 committed by GitHub
commit 2638ad379d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 274 additions and 279 deletions

View File

@ -116,7 +116,7 @@ jobs:
VERSION: ${{ env.VERSION }} VERSION: ${{ env.VERSION }}
DB_CONN: sqlite3 DB_CONN: sqlite3
STATPING_DIR: ${{ github.workspace }} STATPING_DIR: ${{ github.workspace }}
API_KEY: demopassword123 API_SECRET: demopassword123
DISABLE_LOGS: true DISABLE_LOGS: true
ALLOW_REPORTS: true ALLOW_REPORTS: true
PUSH_REQUEST: true PUSH_REQUEST: true
@ -160,25 +160,7 @@ jobs:
uses: matt-ball/newman-action@master uses: matt-ball/newman-action@master
with: with:
postmanApiKey: ${{ secrets.POSTMAN_API }} postmanApiKey: ${{ secrets.POSTMAN_API }}
collection: ./dev/postman.json collection: 1898229-94807b85-ef65-4370-9144-b1a74e04cb0e
environment: ./dev/postman_environment.json environment: ./dev/postman_environment.json
timeoutRequest: 15000 timeoutRequest: 15000
delayRequest: 1000 delayRequest: 1000
pr-slack-update:
needs: [pr-test, pr-test-postman]
runs-on: ubuntu-latest
steps:
- name: Checkout Statping Repo
uses: actions/checkout@v2
- name: Setting ENV's
run: echo ::set-env name=VERSION::$(cat version.txt)
shell: bash
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2.0.0
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_URL }}
SLACK_CHANNEL: pull-requests
SLACK_USERNAME: StatpingDev

View File

@ -113,7 +113,7 @@ jobs:
VERSION: ${{ env.VERSION }} VERSION: ${{ env.VERSION }}
DB_CONN: sqlite3 DB_CONN: sqlite3
STATPING_DIR: ${{ github.workspace }} STATPING_DIR: ${{ github.workspace }}
API_KEY: demopassword123 API_SECRET: demopassword123
DISABLE_LOGS: true DISABLE_LOGS: true
ALLOW_REPORTS: true ALLOW_REPORTS: true
COVERALLS: ${{ secrets.COVERALLS }} COVERALLS: ${{ secrets.COVERALLS }}
@ -182,7 +182,7 @@ jobs:
uses: matt-ball/newman-action@master uses: matt-ball/newman-action@master
with: with:
postmanApiKey: ${{ secrets.POSTMAN_API }} postmanApiKey: ${{ secrets.POSTMAN_API }}
collection: ./dev/postman.json collection: 1898229-94807b85-ef65-4370-9144-b1a74e04cb0e
environment: ./dev/postman_environment.json environment: ./dev/postman_environment.json
timeoutRequest: 15000 timeoutRequest: 15000
delayRequest: 1000 delayRequest: 1000

1
.gitignore vendored
View File

@ -36,3 +36,4 @@ docker
tmp tmp
/frontend/cypress/screenshots/ /frontend/cypress/screenshots/
/frontend/cypress/videos/ /frontend/cypress/videos/
services.yml

View File

@ -1,5 +1,7 @@
# Upcoming # Upcoming
- Added missing information to Mail notification ([#472](https://github.com/statping/statping/issues/472)) - Added missing information to Mail notification ([#472](https://github.com/statping/statping/issues/472))
- Added service.yml file to auto create services (https://github.com/statping/statping/wiki/services.yml)
- Removed Core API_KEY, (unused code, use API_SECRET)
# 0.90.33 (04-24-2020) # 0.90.33 (04-24-2020)
- Fixed config loading method - Fixed config loading method

View File

@ -121,6 +121,10 @@ func start() {
if err := configs.TriggerSamples(); err != nil { if err := configs.TriggerSamples(); err != nil {
exit(errors.Wrap(err, "error creating database")) exit(errors.Wrap(err, "error creating database"))
} }
} else {
if err := core.Samples(); err != nil {
exit(errors.Wrap(err, "error added core details"))
}
} }
} }
@ -153,16 +157,12 @@ func sigterm() {
// mainProcess will initialize the Statping application and run the HTTP server // mainProcess will initialize the Statping application and run the HTTP server
func mainProcess() error { func mainProcess() error {
if err := services.ServicesFromEnvFile(); err != nil {
errStr := "error 'SERVICE' environment variable"
log.Errorln(errStr)
return errors.Wrap(err, errStr)
}
if err := InitApp(); err != nil { if err := InitApp(); err != nil {
return err return err
} }
services.LoadServicesYaml()
if err := handlers.RunHTTPServer(ipAddress, port); err != nil { if err := handlers.RunHTTPServer(ipAddress, port); err != nil {
log.Fatalln(err) log.Fatalln(err)
return errors.Wrap(err, "http server") return errors.Wrap(err, "http server")

View File

@ -27,7 +27,6 @@ services:
# VIRTUAL_PORT: 8888 # VIRTUAL_PORT: 8888
# GO_ENV: test # GO_ENV: test
# DB_CONN: sqlite # DB_CONN: sqlite
# API_KEY: exampleapikey
# API_SECRET: exampleapisecret # API_SECRET: exampleapisecret
# NAME: Statping on SQLite # NAME: Statping on SQLite
# DOMAIN: http://localhost:4000 # DOMAIN: http://localhost:4000
@ -59,7 +58,6 @@ services:
VIRTUAL_HOST: sqlite.dev.statping.com VIRTUAL_HOST: sqlite.dev.statping.com
VIRTUAL_PORT: 8080 VIRTUAL_PORT: 8080
DB_CONN: sqlite DB_CONN: sqlite
API_KEY: exampleapikey
API_SECRET: exampleapisecret API_SECRET: exampleapisecret
NAME: Statping on SQLite NAME: Statping on SQLite
DOMAIN: http://localhost:4000 DOMAIN: http://localhost:4000
@ -96,7 +94,6 @@ services:
DB_DATABASE: statping DB_DATABASE: statping
DB_USER: root DB_USER: root
DB_PASS: password123 DB_PASS: password123
API_KEY: exampleapikey
API_SECRET: exampleapisecret API_SECRET: exampleapisecret
NAME: Statping on MySQL NAME: Statping on MySQL
DOMAIN: http://localhost:4005 DOMAIN: http://localhost:4005
@ -134,7 +131,6 @@ services:
DB_DATABASE: statping DB_DATABASE: statping
DB_USER: root DB_USER: root
DB_PASS: password123 DB_PASS: password123
API_KEY: exampleapikey
API_SECRET: exampleapisecret API_SECRET: exampleapisecret
NAME: Statping on Postgres NAME: Statping on Postgres
DOMAIN: http://localhost:4010 DOMAIN: http://localhost:4010

View File

@ -19,7 +19,6 @@ services:
- ./utils:/go/src/github.com/statping/statping/utils/ - ./utils:/go/src/github.com/statping/statping/utils/
environment: environment:
DB_CONN: sqlite DB_CONN: sqlite
API_KEY: exampleapikey
API_SECRET: exampleapisecret API_SECRET: exampleapisecret
NAME: Statping NAME: Statping
DOMAIN: http://localhost:8585 DOMAIN: http://localhost:8585

35
dev/pwd-stack.yml vendored
View File

@ -7,14 +7,43 @@ services:
image: statping/statping image: statping/statping
ports: ports:
- 8080:8080 - 8080:8080
networks:
- backend
volumes:
- /root/statping:/app
environment: environment:
PORT: 8080 PORT: 8080
SERVICES: '[{"name": "Local Statping", "type": "http", "domain": "http://localhost:8585", "interval": 30}]'
DB_CONN: sqlite
API_KEY: exampleapikey
API_SECRET: exampleapisecret API_SECRET: exampleapisecret
NAME: Statping on SQLite NAME: Statping on SQLite
DOMAIN: http://localhost:8080 DOMAIN: http://localhost:8080
DESCRIPTION: This is a dev environment on SQLite! DESCRIPTION: This is a dev environment on SQLite!
ADMIN_USER: admin ADMIN_USER: admin
ADMIN_PASS: admin ADMIN_PASS: admin
postgres:
hostname: postgres
image: postgres
environment:
POSTGRES_PASSWORD: password123
POSTGRES_DB: statping
POSTGRES_USER: root
networks:
- backend
volumes:
- /root/postgres:/var/lib/postgresql/data
mysql:
hostname: mysql
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: password123
MYSQL_DATABASE: statping
MYSQL_USER: root
MYSQL_PASSWORD: password123
networks:
- backend
volumes:
- /root/mysql:/var/lib/mysql
networks:
backend:

View File

@ -30,7 +30,6 @@ context('Notifier Tests', () => {
cy.visit('/dashboard/settings') cy.visit('/dashboard/settings')
cy.get('#notifiers_tabs > a').should('have.length', 10) cy.get('#notifiers_tabs > a').should('have.length', 10)
cy.get('#api_key').should('not.have.value', '')
cy.get('#api_secret').should('not.have.value', '') cy.get('#api_secret').should('not.have.value', '')
}) })

View File

@ -29,7 +29,6 @@ context('Settings Tests', () => {
cy.visit('/dashboard/settings') cy.visit('/dashboard/settings')
cy.get('#notifiers_tabs > a').should('have.length', 10) cy.get('#notifiers_tabs > a').should('have.length', 10)
cy.get('#api_key').should('not.have.value', '')
cy.get('#api_secret').should('not.have.value', '') cy.get('#api_secret').should('not.have.value', '')
}) })
@ -50,7 +49,6 @@ context('Settings Tests', () => {
cy.get('#description').should('have.value', 'Statping can use Cypress e2e testing to make it more stable!') cy.get('#description').should('have.value', 'Statping can use Cypress e2e testing to make it more stable!')
cy.get('#domain').should('have.value', 'http://localhost:8888') cy.get('#domain').should('have.value', 'http://localhost:8888')
cy.get('#footer').should('have.value', 'Statping Custom Footer') cy.get('#footer').should('have.value', 'Statping Custom Footer')
cy.get('#api_key').should('not.have.value', '')
cy.get('#api_secret').should('not.have.value', '') cy.get('#api_secret').should('not.have.value', '')
}) })

View File

@ -9,28 +9,15 @@
</div> </div>
</div> </div>
<form v-if="loaded && directory" @submit.prevent="saveAssets" :disabled="pending"> <form v-if="loaded && directory" @submit.prevent="saveAssets" :disabled="pending">
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist"> <h3>Variables</h3>
<li class="nav-item col text-center"> <codemirror v-show="loaded" v-model="vars" ref="vars" :options="cmOptions" class="codemirrorInput"/>
<a @click.prevent="changeTab('vars')" class="nav-link" :class="{active: tab === 'vars'}" id="pills-vars-tab" data-toggle="pill" href="#pills-vars" role="tab" aria-controls="pills-vars" aria-selected="true">Variables</a>
</li> <h3 class="mt-3">Base Theme</h3>
<li class="nav-item col text-center"> <codemirror v-show="loaded" v-model="base" ref="base" :options="cmOptions" class="codemirrorInput"/>
<a @click.prevent="changeTab('base')" class="nav-link" :class="{active: tab === 'base'}" id="pills-base-tab" data-toggle="pill" href="#pills-base" role="tab" aria-controls="pills-base" aria-selected="false">Base Theme</a>
</li> <h3 class="mt-3">Mobile Overwrites</h3>
<li class="nav-item col text-center"> <codemirror v-show="loaded" v-model="mobile" ref="mobile" :options="cmOptions" class="codemirrorInput"/>
<a @click.prevent="changeTab('mobile')" class="nav-link" :class="{active: tab === 'mobile'}" id="pills-mobile-tab" data-toggle="pill" href="#pills-mobile" role="tab" aria-controls="pills-mobile" aria-selected="false">Mobile</a>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane show" :class="{active: tab === 'vars'}" id="pills-vars" role="tabpanel" aria-labelledby="pills-vars-tab">
<codemirror v-if="loaded && tab === 'vars'" v-model="vars" ref="vars" :options="cmOptions" class="codemirrorInput"/>
</div>
<div class="tab-pane show" :class="{active: tab === 'base'}" id="pills-base" role="tabpanel" aria-labelledby="pills-base-tab">
<codemirror v-if="loaded && tab === 'base'" v-model="base" ref="base" :options="cmOptions" class="codemirrorInput"/>
</div>
<div class="tab-pane show" :class="{active: tab === 'mobile'}" id="pills-mobile" role="tabpanel" aria-labelledby="pills-mobile-tab">
<codemirror v-if="loaded && tab === 'mobile'" v-model="mobile" ref="mobile" :options="cmOptions" class="codemirrorInput"/>
</div>
</div>
<div v-if="error" class="alert alert-danger mt-3" style="white-space: pre-line;">{{error}}</div> <div v-if="error" class="alert alert-danger mt-3" style="white-space: pre-line;">{{error}}</div>
<button id="save_assets" @submit.prevent="saveAssets" type="submit" class="btn btn-primary btn-block mt-2" :disabled="pending">{{pending ? "Saving..." : "Save Style"}}</button> <button id="save_assets" @submit.prevent="saveAssets" type="submit" class="btn btn-primary btn-block mt-2" :disabled="pending">{{pending ? "Saving..." : "Save Style"}}</button>
@ -66,16 +53,16 @@
}, },
data () { data () {
return { return {
base: "", base: null,
vars: "", vars: null,
mobile: "", mobile: null,
error: null, error: null,
directory: null, directory: null,
tab: "vars", tab: "vars",
loaded: false, loaded: false,
pending: false, pending: false,
cmOptions: { cmOptions: {
height: 600, height: 700,
tabSize: 4, tabSize: 4,
lineNumbers: true, lineNumbers: true,
matchBrackets: true, matchBrackets: true,

View File

@ -35,6 +35,9 @@ export default {
computed: { computed: {
service_txt() { service_txt() {
if (!this.service.online) { if (!this.service.online) {
if (!this.toUnix(this.service.last_success)) {
return `Always Offline`
}
return `Offline for ${this.ago(this.service.last_success)}` return `Offline for ${this.ago(this.service.last_success)}`
} }
return `${this.service.online_24_hours}% Uptime` return `${this.service.online_24_hours}% Uptime`

View File

@ -159,12 +159,15 @@ export default {
smallText(s) { smallText(s) {
const incidents = s.incidents const incidents = s.incidents
if (s.online) { if (s.online) {
return `Online, last checked ${this.ago(s.last_success)}` return `Checked ${this.ago(s.last_success)} ago and responded in ${this.humanTime(s.latency)}`
} else { } else {
const last = s.last_failure const last = s.last_failure
if (last) { if (last) {
return `Offline, last error: ${last} ${this.ago(last.created_at)}` return `Offline, last error: ${last} ${this.ago(last.created_at)}`
} }
if (!this.toUnix(s.last_success)) {
return `Service has never been online`
}
return `Offline` return `Offline`
} }
}, },

View File

@ -169,11 +169,15 @@
}, },
methods: { methods: {
async chartHits(group) { async chartHits(group) {
const start = this.nowSubtract(84600 * 3) const start = this.toUnix(this.nowSubtract(84600 * 3))
this.data = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(new Date()), group, false) const end = this.toUnix(new Date())
if (end-start < 283800) {
group = "5m"
}
this.data = await Api.service_hits(this.service.id, start, end, group, false)
if (this.data.length === 0 && group !== "1h") { if (this.data === null && group !== "5m") {
await this.chartHits("1h") await this.chartHits("10m")
} }
this.series = [{ this.series = [{
name: this.service.name, name: this.service.name,

View File

@ -73,11 +73,11 @@ export default Vue.mixin({
}); });
}, },
serviceLink(service) { serviceLink(service) {
if (!service) { if (service.permalink) {
return "" service = this.$store.getters.serviceByPermalink(service)
} }
if (!service.id) { if (service===undefined) {
service = this.$store.getters.serviceById(service) return `/service/0`
} }
let link = service.permalink ? service.permalink : service.id let link = service.permalink ? service.permalink : service.id
return `/service/${link}` return `/service/${link}`

View File

@ -67,19 +67,6 @@
<div class="card text-black-50 bg-white mt-3"> <div class="card text-black-50 bg-white mt-3">
<div class="card-header">API Settings</div> <div class="card-header">API Settings</div>
<div class="card-body"> <div class="card-body">
<div class="form-group row">
<label class="col-sm-3 col-form-label">API Key</label>
<div class="col-sm-9">
<div class="input-group">
<input v-model="core.api_key" type="text" class="form-control" id="api_key" readonly>
<div class="input-group-append copy-btn">
<button @click.prevent="copy(core.api_key)" class="btn btn-outline-secondary" type="button">Copy</button>
</div>
</div>
<small class="form-text text-muted">API Key can be used for read only routes</small>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-3 col-form-label">API Secret</label> <label class="col-sm-3 col-form-label">API Secret</label>
<div class="col-sm-9"> <div class="col-sm-9">

View File

@ -38,7 +38,6 @@ func apiIndexHandler(r *http.Request) interface{} {
func apiRenewHandler(w http.ResponseWriter, r *http.Request) { func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
var err error var err error
core.App.ApiKey = utils.NewSHA256Hash()
core.App.ApiSecret = utils.NewSHA256Hash() core.App.ApiSecret = utils.NewSHA256Hash()
err = core.App.Update() err = core.App.Update()
if err != nil { if err != nil {

View File

@ -16,7 +16,7 @@ var (
) )
func staticAssets(src string) http.Handler { func staticAssets(src string) http.Handler {
return http.StripPrefix(basePath+src+"/", http.FileServer(http.Dir(utils.Directory+"/assets/"+src))) return http.StripPrefix(src+"/", http.FileServer(http.Dir(utils.Directory+"/assets/"+src)))
} }
// Router returns all of the routes used in Statping. // Router returns all of the routes used in Statping.
@ -80,6 +80,7 @@ func Router() *mux.Router {
api.Handle("/api/clear_cache", authenticated(apiClearCacheHandler, false)) api.Handle("/api/clear_cache", authenticated(apiClearCacheHandler, false))
api.Handle("/api/core", authenticated(apiCoreHandler, false)).Methods("POST") api.Handle("/api/core", authenticated(apiCoreHandler, false)).Methods("POST")
api.Handle("/api/oauth", scoped(apiOAuthHandler)).Methods("GET") api.Handle("/api/oauth", scoped(apiOAuthHandler)).Methods("GET")
api.Handle("/api/oauth/{provider}", http.HandlerFunc(oauthHandler))
api.Handle("/api/logs", authenticated(logsHandler, false)).Methods("GET") api.Handle("/api/logs", authenticated(logsHandler, false)).Methods("GET")
api.Handle("/api/logs/last", authenticated(logsLineHandler, false)).Methods("GET") api.Handle("/api/logs/last", authenticated(logsLineHandler, false)).Methods("GET")

View File

@ -78,7 +78,6 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
c := &core.Core{ c := &core.Core{
Name: project, Name: project,
Description: description, Description: description,
ApiKey: utils.Params.GetString("API_KEY"),
ApiSecret: utils.Params.GetString("API_SECRET"), ApiSecret: utils.Params.GetString("API_SECRET"),
Domain: domain, Domain: domain,
Version: core.App.Version, Version: core.App.Version,

View File

@ -61,7 +61,10 @@ func TestEmailNotifier(t *testing.T) {
To: email.GetValue("var2"), To: email.GetValue("var2"),
Subject: fmt.Sprintf("Service %v is Failing", exampleService.Name), Subject: fmt.Sprintf("Service %v is Failing", exampleService.Name),
Template: mainEmailTemplate, Template: mainEmailTemplate,
Data: exampleService, Data: emailData{
Service: *exampleService,
Failure: *exampleFailure,
},
From: email.GetValue("var1"), From: email.GetValue("var1"),
} }
}) })

View File

@ -40,9 +40,7 @@ func Connect(configs *DbConfig, retry bool) error {
} }
} }
apiKey := p.GetString("API_KEY")
apiSecret := p.GetString("API_SECRET") apiSecret := p.GetString("API_SECRET")
configs.ApiKey = apiKey
configs.ApiSecret = apiSecret configs.ApiSecret = apiSecret
log.WithFields(utils.ToFields(dbSession)).Debugln("connected to database") log.WithFields(utils.ToFields(dbSession)).Debugln("connected to database")

View File

@ -46,9 +46,6 @@ func LoadConfigFile(directory string) (*DbConfig, error) {
if db.Location != "" { if db.Location != "" {
p.Set("LOCATION", db.Location) p.Set("LOCATION", db.Location)
} }
if db.ApiKey != "" {
p.Set("API_KEY", db.ApiKey)
}
if db.ApiSecret != "" { if db.ApiSecret != "" {
p.Set("API_SECRET", db.ApiSecret) p.Set("API_SECRET", db.ApiSecret)
} }

View File

@ -12,7 +12,6 @@ type DbConfig struct {
DbPass string `yaml:"password" json:"-"` DbPass string `yaml:"password" json:"-"`
DbData string `yaml:"database" json:"-"` DbData string `yaml:"database" json:"-"`
DbPort int `yaml:"port" json:"-"` DbPort int `yaml:"port" json:"-"`
ApiKey string `yaml:"api_key" json:"-"`
ApiSecret string `yaml:"api_secret" json:"-"` ApiSecret string `yaml:"api_secret" json:"-"`
Project string `yaml:"-" json:"-"` Project string `yaml:"-" json:"-"`
Description string `yaml:"-" json:"-"` Description string `yaml:"-" json:"-"`

View File

@ -47,7 +47,6 @@ func (c *Core) Create() error {
Name: c.Name, Name: c.Name,
Description: c.Description, Description: c.Description,
ConfigFile: utils.Directory + "/config.yml", ConfigFile: utils.Directory + "/config.yml",
ApiKey: utils.RandomString(32),
ApiSecret: secret, ApiSecret: secret,
Version: App.Version, Version: App.Version,
Domain: c.Domain, Domain: c.Domain,

View File

@ -6,18 +6,15 @@ import (
) )
func Samples() error { func Samples() error {
apiKey := utils.Params.GetString("API_KEY")
apiSecret := utils.Params.GetString("API_SECRET") apiSecret := utils.Params.GetString("API_SECRET")
if apiKey == "" || apiSecret == "" { if apiSecret == "" {
apiKey = utils.RandomString(32)
apiSecret = utils.RandomString(32) apiSecret = utils.RandomString(32)
} }
core := &Core{ core := &Core{
Name: "Statping Sample Data", Name: "Statping Sample Data",
Description: "This data is only used to testing", Description: "This data is only used to testing",
ApiKey: apiKey,
ApiSecret: apiSecret, ApiSecret: apiSecret,
Domain: "http://localhost:8080", Domain: "http://localhost:8080",
CreatedAt: utils.Now(), CreatedAt: utils.Now(),

View File

@ -23,7 +23,6 @@ type Core struct {
Name string `gorm:"not null;column:name" json:"name,omitempty"` Name string `gorm:"not null;column:name" json:"name,omitempty"`
Description string `gorm:"not null;column:description" json:"description,omitempty"` Description string `gorm:"not null;column:description" json:"description,omitempty"`
ConfigFile string `gorm:"column:config" json:"-"` ConfigFile string `gorm:"column:config" json:"-"`
ApiKey string `gorm:"column:api_key" json:"api_key" scope:"admin"`
ApiSecret string `gorm:"column:api_secret" json:"api_secret" scope:"admin"` ApiSecret string `gorm:"column:api_secret" json:"api_secret" scope:"admin"`
Style string `gorm:"not null;column:style" json:"style,omitempty"` Style string `gorm:"not null;column:style" json:"style,omitempty"`
Footer null.NullString `gorm:"column:footer" json:"footer"` Footer null.NullString `gorm:"column:footer" json:"footer"`

View File

@ -72,13 +72,17 @@ type IntResult struct {
func (h Hitters) Avg() int64 { func (h Hitters) Avg() int64 {
var r IntResult var r IntResult
var q database.Database
switch h.db.DbType() { switch h.db.DbType() {
case "mysql": case "mysql":
h.db.Select("CAST(AVG(latency) as UNSIGNED INTEGER) as amount").Scan(&r) q = h.db.Select("CAST(AVG(latency) as UNSIGNED INTEGER) as amount")
case "postgres": case "postgres":
h.db.Select("CAST(AVG(latency) as bigint) as amount").Scan(&r) q = h.db.Select("CAST(AVG(latency) as bigint) as amount")
default: default:
h.db.Select("CAST(AVG(latency) as INT) as amount").Scan(&r) q = h.db.Select("CAST(AVG(latency) as INT) as amount")
}
if err := q.Scan(&r).Error(); err != nil {
log.Errorln(err)
} }
return r.Amount return r.Amount
} }

View File

@ -1,6 +1,9 @@
package null package null
import "encoding/json" import (
"encoding/json"
"gopkg.in/yaml.v2"
)
// MarshalJSON for NullInt64 // MarshalJSON for NullInt64
func (i NullInt64) MarshalJSON() ([]byte, error) { func (i NullInt64) MarshalJSON() ([]byte, error) {
@ -33,3 +36,35 @@ func (s NullString) MarshalJSON() ([]byte, error) {
} }
return json.Marshal(s.String) return json.Marshal(s.String)
} }
// MarshalYAML for NullInt64
func (i NullInt64) MarshalYAML() (interface{}, error) {
if !i.Valid {
return 0, nil
}
return yaml.Marshal(i.Int64)
}
// MarshalYAML for NullFloat64
func (f NullFloat64) MarshalYAML() (interface{}, error) {
if !f.Valid {
return 0.0, nil
}
return yaml.Marshal(f.Float64)
}
// MarshalYAML for NullBool
func (bb NullBool) MarshalYAML() (interface{}, error) {
if !bb.Valid {
return false, nil
}
return yaml.Marshal(bb.Bool)
}
// MarshalYAML for NullString
func (s NullString) MarshalYAML() (interface{}, error) {
if !s.Valid {
return "", nil
}
return yaml.Marshal(s.String)
}

View File

@ -29,3 +29,43 @@ func (s *NullString) UnmarshalJSON(b []byte) error {
s.Valid = (err == nil) s.Valid = (err == nil)
return err return err
} }
// UnmarshalYAML for NullInt64
func (i *NullInt64) UnmarshalYAML(unmarshal func(interface{}) error) error {
var val int64
if err := unmarshal(&val); err != nil {
return err
}
*i = NewNullInt64(val)
return nil
}
// UnmarshalYAML for NullFloat64
func (f *NullFloat64) UnmarshalYAML(unmarshal func(interface{}) error) error {
var val float64
if err := unmarshal(&val); err != nil {
return err
}
*f = NewNullFloat64(val)
return nil
}
// UnmarshalYAML for NullBool
func (bb *NullBool) UnmarshalYAML(unmarshal func(interface{}) error) error {
var val bool
if err := unmarshal(&val); err != nil {
return err
}
*bb = NewNullBool(val)
return nil
}
// UnmarshalYAML for NullFloat64
func (s *NullString) UnmarshalYAML(unmarshal func(interface{}) error) error {
var val string
if err := unmarshal(&val); err != nil {
return err
}
*s = NewNullString(val)
return nil
}

View File

@ -1,59 +0,0 @@
package services
import (
"bufio"
"github.com/pkg/errors"
"github.com/statping/statping/utils"
"os"
)
// findServiceByHas will return a service that matches the SHA256 hash of a service
// Service hash example: sha256(name:EXAMPLEdomain:HTTP://DOMAIN.COMport:8080type:HTTPmethod:GET)
func findServiceByHash(hash string) *Service {
for _, service := range All() {
if service.Hash() == hash {
return service
}
}
return nil
}
func ServicesFromEnvFile() error {
servicesEnv := utils.Params.GetString("SERVICES_FILE")
if servicesEnv == "" {
return nil
}
file, err := os.Open(servicesEnv)
if err != nil {
return errors.Wrapf(err, "error opening 'SERVICES_FILE' at: %s", servicesEnv)
}
defer file.Close()
var serviceLines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
serviceLines = append(serviceLines, scanner.Text())
}
if len(serviceLines) == 0 {
return nil
}
for k, service := range serviceLines {
svr, err := ValidateService(service)
if err != nil {
return errors.Wrapf(err, "invalid service at index %d in SERVICES_FILE environment variable", k)
}
if findServiceByHash(svr.Hash()) == nil {
if err := svr.Create(); err != nil {
return errors.Wrapf(err, "could not create service %s", svr.Name)
}
log.Infof("Created new service '%s'", svr.Name)
}
}
return nil
}

View File

@ -8,12 +8,9 @@ import (
"github.com/statping/statping/types" "github.com/statping/statping/types"
"github.com/statping/statping/types/failures" "github.com/statping/statping/types/failures"
"github.com/statping/statping/types/hits" "github.com/statping/statping/types/hits"
"github.com/statping/statping/types/null"
"github.com/statping/statping/utils" "github.com/statping/statping/utils"
"net/url"
"sort" "sort"
"strconv" "strconv"
"strings"
"time" "time"
) )
@ -233,57 +230,6 @@ func SelectAllServices(start bool) (map[int64]*Service, error) {
return allServices, nil return allServices, nil
} }
func ValidateService(line string) (*Service, error) {
p, err := url.Parse(line)
if err != nil {
return nil, err
}
newService := new(Service)
domain := p.Host
newService.Name = niceDomainName(domain, p.Path)
if p.Port() != "" {
newService.Port = int(utils.ToInt(p.Port()))
if p.Scheme != "http" && p.Scheme != "https" {
domain = strings.ReplaceAll(domain, ":"+p.Port(), "")
}
}
newService.Domain = domain
switch p.Scheme {
case "http", "https":
newService.Type = "http"
newService.Method = "get"
if p.Scheme == "https" {
newService.VerifySSL = null.NewNullBool(true)
}
default:
newService.Type = p.Scheme
}
return newService, nil
}
func niceDomainName(domain string, paths string) string {
domain = strings.ReplaceAll(domain, "www.", "")
splitPath := strings.Split(paths, "/")
if len(splitPath) == 1 {
return domain
}
var addedName []string
for k, p := range splitPath {
if k > 2 {
break
}
if len(p) > 16 {
addedName = append(addedName, p+"...")
break
} else {
addedName = append(addedName, p)
}
}
return domain + strings.Join(addedName, "/")
}
func (s *Service) UpdateStats() *Service { func (s *Service) UpdateStats() *Service {
s.Online24Hours = s.OnlineDaysPercent(1) s.Online24Hours = s.OnlineDaysPercent(1)
s.Online7Days = s.OnlineDaysPercent(7) s.Online7Days = s.OnlineDaysPercent(7)

View File

@ -232,8 +232,6 @@ func CheckHttp(s *Service, record bool) *Service {
headers = strings.Split(s.Headers.String, ",") headers = strings.Split(s.Headers.String, ",")
} else { } else {
headers = nil headers = nil
log.Warnf("Custom set Headers are not valid for Server '%s'!\n",
s.Name)
} }
// check if 'Content-Type' header was defined // check if 'Content-Type' header was defined

View File

@ -22,56 +22,56 @@ func Services() map[int64]*Service {
// Service is the main struct for Services // Service is the main struct for Services
type Service struct { type Service struct {
Id int64 `gorm:"primary_key;column:id" json:"id"` Id int64 `gorm:"primary_key;column:id" json:"id" yaml:"id"`
Name string `gorm:"column:name" json:"name"` Name string `gorm:"column:name" json:"name" yaml:"name"`
Domain string `gorm:"column:domain" json:"domain" private:"true" scope:"user,admin"` Domain string `gorm:"column:domain" json:"domain" yaml:"domain" private:"true" scope:"user,admin"`
Expected null.NullString `gorm:"column:expected" json:"expected" scope:"user,admin"` Expected null.NullString `gorm:"column:expected" json:"expected" yaml:"expected" scope:"user,admin"`
ExpectedStatus int `gorm:"default:200;column:expected_status" json:"expected_status" scope:"user,admin"` ExpectedStatus int `gorm:"default:200;column:expected_status" json:"expected_status" yaml:"expected_status" scope:"user,admin"`
Interval int `gorm:"default:30;column:check_interval" json:"check_interval"` Interval int `gorm:"default:30;column:check_interval" json:"check_interval" yaml:"check_interval"`
Type string `gorm:"column:check_type" json:"type" scope:"user,admin"` Type string `gorm:"column:check_type" json:"type" scope:"user,admin" yaml:"type"`
Method string `gorm:"column:method" json:"method" scope:"user,admin"` Method string `gorm:"column:method" json:"method" scope:"user,admin" yaml:"method"`
PostData null.NullString `gorm:"column:post_data" json:"post_data" scope:"user,admin"` PostData null.NullString `gorm:"column:post_data" json:"post_data" scope:"user,admin" yaml:"post_data"`
Port int `gorm:"not null;column:port" json:"port" scope:"user,admin"` Port int `gorm:"not null;column:port" json:"port" scope:"user,admin" yaml:"port"`
Timeout int `gorm:"default:30;column:timeout" json:"timeout" scope:"user,admin"` Timeout int `gorm:"default:30;column:timeout" json:"timeout" scope:"user,admin" yaml:"timeout"`
Order int `gorm:"default:0;column:order_id" json:"order_id"` Order int `gorm:"default:0;column:order_id" json:"order_id" yaml:"order_id"`
VerifySSL null.NullBool `gorm:"default:false;column:verify_ssl" json:"verify_ssl" scope:"user,admin"` VerifySSL null.NullBool `gorm:"default:false;column:verify_ssl" json:"verify_ssl" scope:"user,admin" yaml:"verify_ssl"`
Public null.NullBool `gorm:"default:true;column:public" json:"public"` Public null.NullBool `gorm:"default:true;column:public" json:"public" yaml:"public"`
GroupId int `gorm:"default:0;column:group_id" json:"group_id"` GroupId int `gorm:"default:0;column:group_id" json:"group_id" yaml:"group_id"`
Headers null.NullString `gorm:"column:headers" json:"headers" scope:"user,admin"` Headers null.NullString `gorm:"column:headers" json:"headers" scope:"user,admin" yaml:"headers"`
Permalink null.NullString `gorm:"column:permalink" json:"permalink"` Permalink null.NullString `gorm:"column:permalink;unique;" json:"permalink" yaml:"permalink"`
Redirect null.NullBool `gorm:"default:false;column:redirect" json:"redirect" scope:"user,admin"` Redirect null.NullBool `gorm:"default:false;column:redirect" json:"redirect" scope:"user,admin" yaml:"redirect"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` CreatedAt time.Time `gorm:"column:created_at" json:"created_at" yaml:"-"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" yaml:"-"`
Online bool `gorm:"-" json:"online"` Online bool `gorm:"-" json:"online" yaml:"-"`
Latency int64 `gorm:"-" json:"latency"` Latency int64 `gorm:"-" json:"latency" yaml:"-"`
PingTime int64 `gorm:"-" json:"ping_time"` PingTime int64 `gorm:"-" json:"ping_time" yaml:"-"`
Online24Hours float32 `gorm:"-" json:"online_24_hours"` Online24Hours float32 `gorm:"-" json:"online_24_hours" yaml:"-"`
Online7Days float32 `gorm:"-" json:"online_7_days"` Online7Days float32 `gorm:"-" json:"online_7_days" yaml:"-"`
AvgResponse int64 `gorm:"-" json:"avg_response"` AvgResponse int64 `gorm:"-" json:"avg_response" yaml:"-"`
FailuresLast24Hours int `gorm:"-" json:"failures_24_hours"` FailuresLast24Hours int `gorm:"-" json:"failures_24_hours" yaml:"-"`
Running chan bool `gorm:"-" json:"-"` Running chan bool `gorm:"-" json:"-" yaml:"-"`
Checkpoint time.Time `gorm:"-" json:"-"` Checkpoint time.Time `gorm:"-" json:"-" yaml:"-"`
SleepDuration time.Duration `gorm:"-" json:"-"` SleepDuration time.Duration `gorm:"-" json:"-" yaml:"-"`
LastResponse string `gorm:"-" json:"-"` LastResponse string `gorm:"-" json:"-" yaml:"-"`
NotifyAfter int64 `gorm:"column:notify_after" json:"notify_after" scope:"user,admin"` NotifyAfter int64 `gorm:"column:notify_after" json:"notify_after" yaml:"notify_after" scope:"user,admin"`
notifyAfterCount int64 `gorm:"-" json:"-"` notifyAfterCount int64 `gorm:"-" json:"-" yaml:"-"`
AllowNotifications null.NullBool `gorm:"default:true;column:allow_notifications" json:"allow_notifications" scope:"user,admin"` AllowNotifications null.NullBool `gorm:"default:true;column:allow_notifications" json:"allow_notifications" yaml:"allow_notifications" scope:"user,admin"`
UserNotified bool `gorm:"-" json:"-"` // True if the User was already notified about a Downtime UserNotified bool `gorm:"-" json:"-" yaml:"-"` // True if the User was already notified about a Downtime
UpdateNotify null.NullBool `gorm:"default:true;column:notify_all_changes" json:"notify_all_changes" scope:"user,admin"` // This Variable is a simple copy of `core.CoreApp.UpdateNotify.Bool` UpdateNotify null.NullBool `gorm:"default:true;column:notify_all_changes" json:"notify_all_changes" yaml:"notify_all_changes" scope:"user,admin"` // This Variable is a simple copy of `core.CoreApp.UpdateNotify.Bool`
DownText string `gorm:"-" json:"-"` // Contains the current generated Downtime Text DownText string `gorm:"-" json:"-" yaml:"-"` // Contains the current generated Downtime Text
SuccessNotified bool `gorm:"-" json:"-"` // Is 'true' if the user has already be informed that the Services now again available SuccessNotified bool `gorm:"-" json:"-" yaml:"-"` // Is 'true' if the user has already be informed that the Services now again available
LastStatusCode int `gorm:"-" json:"status_code"` LastStatusCode int `gorm:"-" json:"status_code" yaml:"-"`
Failures []*failures.Failure `gorm:"-" json:"failures,omitempty" scope:"user,admin"` Failures []*failures.Failure `gorm:"-" json:"failures,omitempty" yaml:"-" scope:"user,admin"`
AllCheckins []*checkins.Checkin `gorm:"-" json:"checkins,omitempty" scope:"user,admin"` AllCheckins []*checkins.Checkin `gorm:"-" json:"checkins,omitempty" yaml:"-" scope:"user,admin"`
LastLookupTime int64 `gorm:"-" json:"-"` LastLookupTime int64 `gorm:"-" json:"-" yaml:"-"`
LastLatency int64 `gorm:"-" json:"-"` LastLatency int64 `gorm:"-" json:"-" yaml:"-"`
LastCheck time.Time `gorm:"-" json:"-"` LastCheck time.Time `gorm:"-" json:"-" yaml:"-"`
LastOnline time.Time `gorm:"-" json:"last_success"` LastOnline time.Time `gorm:"-" json:"last_success" yaml:"-"`
LastOffline time.Time `gorm:"-" json:"last_error"` LastOffline time.Time `gorm:"-" json:"last_error" yaml:"-"`
Stats *Stats `gorm:"-" json:"stats,omitempty"` Stats *Stats `gorm:"-" json:"stats,omitempty" yaml:"-"`
SecondsOnline int64 `gorm:"-" json:"-"` SecondsOnline int64 `gorm:"-" json:"-" yaml:"-"`
SecondsOffline int64 `gorm:"-" json:"-"` SecondsOffline int64 `gorm:"-" json:"-" yaml:"-"`
} }
type Stats struct { type Stats struct {

52
types/services/yaml.go Normal file
View File

@ -0,0 +1,52 @@
package services
import (
"github.com/pkg/errors"
"github.com/statping/statping/utils"
"gopkg.in/yaml.v2"
)
type yamlFile struct {
Services []*Service `yaml:"services,flow"`
}
// LoadServicesYaml will attempt to load the 'services.yml' file for Service Auto Creation on startup.
func LoadServicesYaml() (*yamlFile, error) {
f, err := utils.OpenFile(utils.Directory + "/services.yml")
if err != nil {
return nil, err
}
var svrs *yamlFile
if err := yaml.Unmarshal([]byte(f), &svrs); err != nil {
log.Errorln("Unable to parse the services.yml file", err)
return nil, err
}
log.Infof("Found %d services inside services.yml file", len(svrs.Services))
for _, svr := range svrs.Services {
log.Infof("Service %s %d, hash: %s", svr.Name, svr.Id, svr.Hash())
if findServiceByHash(svr.Hash()) == nil {
if err := svr.Create(); err != nil {
return nil, errors.Wrapf(err, "could not create service %s", svr.Name)
}
log.Infof("Automatically created service '%s' checking %s", svr.Name, svr.Domain)
go ServiceCheckQueue(svr, true)
}
}
return svrs, nil
}
// findServiceByHas will return a service that matches the SHA256 hash of a service
// Service hash example: sha256(name:EXAMPLEdomain:HTTP://DOMAIN.COMport:8080type:HTTPmethod:GET)
func findServiceByHash(hash string) *Service {
for _, service := range All() {
if service.Hash() == hash {
return service
}
}
return nil
}

View File

@ -8,13 +8,13 @@ import (
func ReplaceTemplate(tmpl string, data interface{}) string { func ReplaceTemplate(tmpl string, data interface{}) string {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
slackTemp, err := template.New("replacement").Parse(tmpl) tmp, err := template.New("replacement").Parse(tmpl)
if err != nil { if err != nil {
Log.Error(err) Log.Error(err)
return err.Error() return err.Error()
} }
err = slackTemp.Execute(buf, data) err = tmp.Execute(buf, data)
if err != nil { if err != nil {
Log.Error(err) Log.Error(err)
return err.Error() return err.Error()

View File

@ -41,18 +41,16 @@ func TestCommand(t *testing.T) {
} }
func TestReplaceTemplate(t *testing.T) { func TestReplaceTemplate(t *testing.T) {
type Object struct { type Object struct {
Id int64 Id int64
String string String string
Online bool Online bool
Example string Example string
} }
example := &Object{ ex := &Object{
1, "this is an example", true, "it should work", 1, "this is an example", true, "it should work",
} }
result := ReplaceTemplate(`{"id": {{.Id}} }`, ex)
result := ReplaceTemplate(`{"id": {{.Object.Id}} }`, example)
assert.Equal(t, "{\"id\": 1 }", result) assert.Equal(t, "{\"id\": 1 }", result)
} }

View File

@ -1 +1 @@
0.90.33 0.90.34