diff --git a/cmd/main.go b/cmd/main.go index 91eabcd3..3e66d75c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -131,10 +131,6 @@ func main() { exit(err) } - if err = confgs.VerifyMigration(); err != nil { - exit(err) - } - exists := confgs.Db.HasTable("core") if !exists { var srvs int64 @@ -161,6 +157,10 @@ func main() { } + if err = confgs.DatabaseChanges(); err != nil { + exit(err) + } + if err := confgs.MigrateDatabase(); err != nil { exit(err) } diff --git a/database/grouping.go b/database/grouping.go index 86dbdc00..fa383295 100644 --- a/database/grouping.go +++ b/database/grouping.go @@ -1,6 +1,7 @@ package database import ( + "errors" "fmt" "github.com/statping/statping/types" "github.com/statping/statping/utils" @@ -144,7 +145,7 @@ type isObject interface { Db() Database } -func ParseQueries(r *http.Request, o isObject) *GroupQuery { +func ParseQueries(r *http.Request, o isObject) (*GroupQuery, error) { fields := parseGet(r) grouping := fields.Get("group") startField := utils.ToInt(fields.Get("start")) @@ -179,11 +180,15 @@ func ParseQueries(r *http.Request, o isObject) *GroupQuery { db: q, } + if query.Start.After(query.End) { + return nil, errors.New("start time is after ending time") + } + if startField == 0 { - query.Start = time.Now().Add(-7 * types.Day).UTC() + query.Start = utils.Now().Add(-7 * types.Day) } if endField == 0 { - query.End = time.Now().UTC() + query.End = utils.Now() } if query.End.After(utils.Now()) { query.End = utils.Now() @@ -203,7 +208,7 @@ func ParseQueries(r *http.Request, o isObject) *GroupQuery { } query.db = q - return query + return query, nil } func parseForm(r *http.Request) url.Values { diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 53093d21..85f7438e 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,6 +1,6 @@ @@ -19,10 +19,13 @@ loaded: false, version: "", logged_in: false, + app: null } }, async created() { - await this.$store.dispatch('loadRequired') + this.app = await this.$store.dispatch('loadRequired') + + this.app = {...this.$store.state} if (this.$store.getters.core.logged_in) { await this.$store.dispatch('loadAdmin') diff --git a/frontend/src/assets/scss/base.scss b/frontend/src/assets/scss/base.scss index 74dcf4ab..5dee77e0 100644 --- a/frontend/src/assets/scss/base.scss +++ b/frontend/src/assets/scss/base.scss @@ -6,7 +6,34 @@ HTML,BODY { } .index-chart { - height: 490px; + height: $service-card-height; +} + +.sub-service-card { + border: 1px solid #dcdcdc87; + padding-top: 7px; + padding-bottom: 10px; + height: 155px; +} + +.expanded-service { + height: 89vh; + /*Animation*/ + -webkit-transition: height 2s ease; + -moz-transition: height 2s ease; + -o-transition: height 2s ease; + -ms-transition: height 2s ease; + transition: height 2s ease; + + .stats_area { + display: none; + } + +} + +.service-chart { + bottom: -15px; + position: relative; } .chartmarker { @@ -167,7 +194,19 @@ HTML,BODY { } .font-5 { - font-size: 20pt; + font-size: 17pt; +} + +.font-6 { + font-size: 24pt; +} + +.font-7 { + font-size: 31pt; +} + +.font-8 { + font-size: 38pt; } .badge { @@ -225,6 +264,7 @@ HTML,BODY { .card-body H4 A { color: $service-title; + font-size: $service-title-size; text-decoration: none; } @@ -248,7 +288,7 @@ HTML,BODY { .service-chart-heatmap { position: relative; - height: 300px; + height: 180px; width: 100%; } diff --git a/frontend/src/assets/scss/mobile.scss b/frontend/src/assets/scss/mobile.scss index 9c7587b4..2e45ba79 100644 --- a/frontend/src/assets/scss/mobile.scss +++ b/frontend/src/assets/scss/mobile.scss @@ -7,7 +7,7 @@ } .index-chart { - height: 33vh; + height: 380px; } .sm-container { diff --git a/frontend/src/assets/scss/variables.scss b/frontend/src/assets/scss/variables.scss index e0c15b0b..079940a1 100644 --- a/frontend/src/assets/scss/variables.scss +++ b/frontend/src/assets/scss/variables.scss @@ -8,9 +8,11 @@ $description-color: #939393; $service-background: #ffffff; $service-border: 1px solid rgba(0,0,0,.125); $service-title: #444444; +$service-title-size: 1.8rem; $service-stats-color: #4f4f4f; $service-description-color: #fff; $service-stats-size: 2.3rem; +$service-card-height: 490px; /* Button Colors */ $success-color: #47d337; @@ -25,7 +27,6 @@ $footer-display: block; /* Global Settings */ $global-border-radius: 0.2rem; - /* Mobile Settings */ $sm-background-color: #fcfcfc; $sm-border-radius: 0rem; diff --git a/frontend/src/components/Index/Group.vue b/frontend/src/components/Index/Group.vue index 3d9c1e10..52dfd306 100644 --- a/frontend/src/components/Index/Group.vue +++ b/frontend/src/components/Index/Group.vue @@ -5,7 +5,7 @@ {{service.name}} - {{service.online ? "ONLINE" : "OFFLINE"}} + {{service.online ? "ONLINE" : "OFFLINE"}} diff --git a/frontend/src/components/Index/Header.vue b/frontend/src/components/Index/Header.vue index f1a12936..c689efc8 100644 --- a/frontend/src/components/Index/Header.vue +++ b/frontend/src/components/Index/Header.vue @@ -1,6 +1,6 @@ diff --git a/frontend/src/components/Index/MessageBlock.vue b/frontend/src/components/Index/MessageBlock.vue index 1c0139a9..5fd9ce57 100644 --- a/frontend/src/components/Index/MessageBlock.vue +++ b/frontend/src/components/Index/MessageBlock.vue @@ -1,13 +1,13 @@ diff --git a/frontend/src/components/Service/Analytics.vue b/frontend/src/components/Service/Analytics.vue new file mode 100644 index 00000000..f8dbdf32 --- /dev/null +++ b/frontend/src/components/Service/Analytics.vue @@ -0,0 +1,74 @@ + + + + + + diff --git a/frontend/src/components/Service/MiniSparkLine.vue b/frontend/src/components/Service/MiniSparkLine.vue new file mode 100644 index 00000000..12f8aa38 --- /dev/null +++ b/frontend/src/components/Service/MiniSparkLine.vue @@ -0,0 +1,85 @@ + + + + + + diff --git a/frontend/src/components/Service/ServiceBlock.vue b/frontend/src/components/Service/ServiceBlock.vue index 685ef6a6..24e02514 100644 --- a/frontend/src/components/Service/ServiceBlock.vue +++ b/frontend/src/components/Service/ServiceBlock.vue @@ -1,26 +1,35 @@ - diff --git a/frontend/src/routes.js b/frontend/src/routes.js index df31db98..15c57b09 100644 --- a/frontend/src/routes.js +++ b/frontend/src/routes.js @@ -27,7 +27,6 @@ const routes = [ }, { path: '/dashboard', - name: 'Dashboard', component: Dashboard, meta: { requiresAuth: true diff --git a/frontend/src/store.js b/frontend/src/store.js index fb3fafcd..5b64ec25 100644 --- a/frontend/src/store.js +++ b/frontend/src/store.js @@ -25,8 +25,7 @@ export default new Vuex.Store({ groups: [], messages: [], users: [], - notifiers: [], - integrations: [] + notifiers: [] }, getters: { hasAllData: state => state.hasAllData, @@ -38,13 +37,19 @@ export default new Vuex.Store({ messages: state => state.messages, users: state => state.users, notifiers: state => state.notifiers, - integrations: state => state.integrations, servicesInOrder: state => state.services.sort((a, b) => a.order_id - b.order_id), groupsInOrder: state => state.groups.sort((a, b) => a.order_id - b.order_id), groupsClean: state => state.groups.filter(g => g.name !== '').sort((a, b) => a.order_id - b.order_id), groupsCleanInOrder: state => state.groups.filter(g => g.name !== '').sort((a, b) => a.order_id - b.order_id).sort((a, b) => a.order_id - b.order_id), + serviceByAll: (state) => (element) => { + if (element % 1 === 0) { + return state.services.find(s => s.id == element) + } else { + return state.services.find(s => s.permalink === element) + } + }, serviceById: (state) => (id) => { return state.services.find(s => s.id == id) }, @@ -100,12 +105,13 @@ export default new Vuex.Store({ }, setNotifiers (state, notifiers) { state.notifiers = notifiers - }, - setIntegrations (state, integrations) { - state.integrations = integrations } }, actions: { + async getAllServices(context) { + const services = await Api.services() + context.commit("setServices", services); + }, async loadRequired(context) { const core = await Api.core() context.commit("setCore", core); @@ -140,8 +146,6 @@ export default new Vuex.Store({ context.commit("setNotifiers", notifiers); const users = await Api.users() context.commit("setUsers", users); - const integrations = await Api.integrations() - context.commit("setIntegrations", integrations); } } }); diff --git a/handlers/services.go b/handlers/services.go index eea4b37b..4c1d2126 100644 --- a/handlers/services.go +++ b/handlers/services.go @@ -128,7 +128,11 @@ func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) { return } - groupQuery := database.ParseQueries(r, service.AllHits()) + groupQuery, err := database.ParseQueries(r, service.AllHits()) + if err != nil { + sendErrorJson(err, w, r) + return + } objs, err := groupQuery.GraphData(database.ByAverage("latency", 1000)) if err != nil { @@ -146,7 +150,11 @@ func apiServiceFailureDataHandler(w http.ResponseWriter, r *http.Request) { return } - groupQuery := database.ParseQueries(r, service.AllFailures()) + groupQuery, err := database.ParseQueries(r, service.AllFailures()) + if err != nil { + sendErrorJson(err, w, r) + return + } objs, err := groupQuery.GraphData(database.ByCount) if err != nil { @@ -164,7 +172,11 @@ func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) { return } - groupQuery := database.ParseQueries(r, service.AllHits()) + groupQuery, err := database.ParseQueries(r, service.AllHits()) + if err != nil { + sendErrorJson(err, w, r) + return + } objs, err := groupQuery.GraphData(database.ByAverage("ping_time", 1000)) if err != nil { @@ -216,7 +228,11 @@ func apiServiceFailuresHandler(r *http.Request) interface{} { } var fails []*failures.Failure - database.ParseQueries(r, service.AllFailures()).Find(&fails) + query, err := database.ParseQueries(r, service.AllFailures()) + if err != nil { + return err + } + query.Find(&fails) return fails } @@ -228,6 +244,10 @@ func apiServiceHitsHandler(r *http.Request) interface{} { } var hts []*hits.Hit - database.ParseQueries(r, service.AllHits()).Find(&hts) + query, err := database.ParseQueries(r, service.AllHits()) + if err != nil { + return err + } + query.Find(&hts) return hts } diff --git a/types/configs/latest_sql.go b/types/configs/latest_sql.go new file mode 100644 index 00000000..6d381fdb --- /dev/null +++ b/types/configs/latest_sql.go @@ -0,0 +1,64 @@ +package configs + +import ( + "fmt" + "github.com/statping/statping/utils" + "os" +) + +const latestMigration = 1583860000 + +func init() { + os.Setenv("MIGRATION_ID", utils.ToString(latestMigration)) +} + +func (c *DbConfig) genericMigration(alterStr string) error { + if err := c.Db.Exec(fmt.Sprintf("ALTER TABLE hits %s COLUMN latency TYPE BIGINT;", alterStr)).Error(); err != nil { + return err + } + if err := c.Db.Exec(fmt.Sprintf("ALTER TABLE hits %s COLUMN ping_time TYPE BIGINT;", alterStr)).Error(); err != nil { + return err + } + if err := c.Db.Exec(fmt.Sprintf("ALTER TABLE failures %s COLUMN latency TYPE BIGINT;", alterStr)).Error(); err != nil { + return err + } + if err := c.Db.Exec("UPDATE hits SET latency = CAST(latency * 1000000 AS bigint);").Error(); err != nil { + return err + } + if err := c.Db.Exec("UPDATE hits SET ping_time = CAST(ping_time * 1000000 AS bigint);").Error(); err != nil { + return err + } + if err := c.Db.Exec("UPDATE failures SET ping_time = CAST(ping_time * 1000000 AS bigint);").Error(); err != nil { + return err + } + return nil +} + +func (c *DbConfig) sqliteMigration() error { + if err := c.Db.Exec(`ALTER TABLE hits RENAME TO hits_backup;`).Error(); err != nil { + return err + } + if err := c.Db.Exec(`CREATE TABLE hits (id INTEGER PRIMARY KEY AUTOINCREMENT, service bigint, latency bigint, ping_time bigint, created_at datetime);`).Error(); err != nil { + return err + } + if err := c.Db.Exec(`INSERT INTO hits (id, service, latency, ping_time, created_at) SELECT id, service, CAST(latency * 1000000 AS bigint), CAST(ping_time * 1000000 AS bigint), created_at FROM hits_backup;`).Error(); err != nil { + return err + } + // failures table + if err := c.Db.Exec(`ALTER TABLE failures RENAME TO failures_backup;`).Error(); err != nil { + return err + } + if err := c.Db.Exec(`CREATE TABLE failures (id INTEGER PRIMARY KEY AUTOINCREMENT, issue varchar(255), method varchar(255), method_id bigint, service bigint, ping_time bigint, checkin bigint, error_code bigint, created_at datetime);`).Error(); err != nil { + return err + } + if err := c.Db.Exec(`INSERT INTO failures (id, issue, method, method_id, service, ping_time, checkin, created_at) SELECT id, issue, method, method_id, service, CAST(ping_time * 1000000 AS bigint), checkin, created_at FROM failures_backup;`).Error(); err != nil { + return err + } + if err := c.Db.Exec(`DROP TABLE hits_backup;`).Error(); err != nil { + return err + } + if err := c.Db.Exec(`DROP TABLE failures_backup;`).Error(); err != nil { + return err + } + return nil +} diff --git a/types/configs/migration.go b/types/configs/migration.go index 7d3ac98b..b9fe0d83 100644 --- a/types/configs/migration.go +++ b/types/configs/migration.go @@ -2,6 +2,9 @@ package configs import ( "fmt" + _ "github.com/jinzhu/gorm/dialects/mysql" + _ "github.com/jinzhu/gorm/dialects/postgres" + _ "github.com/jinzhu/gorm/dialects/sqlite" "github.com/statping/statping/types/checkins" "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" @@ -12,53 +15,36 @@ import ( "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/services" "github.com/statping/statping/types/users" - - _ "github.com/jinzhu/gorm/dialects/mysql" - _ "github.com/jinzhu/gorm/dialects/postgres" - _ "github.com/jinzhu/gorm/dialects/sqlite" ) -// InsertNotifierDB inject the Statping database instance to the Notifier package -//func (c *DbConfig) InsertNotifierDB() error { -// if !database.Available() { -// err := c.Connect() -// if err != nil { -// return errors.New("database connection has not been created") -// } -// } -// notifiers.SetDB(database.DB()) -// return nil -//} +func (c *DbConfig) DatabaseChanges() error { + var cr core.Core + c.Db.Model(&core.Core{}).Find(&cr) -// InsertIntegratorDB inject the Statping database instance to the Integrations package -//func (c *DbConfig) InsertIntegratorDB() error { -// if !database.Available() { -// err := c.Connect() -// if err != nil { -// return errors.Wrap(err,"database connection has not been created") -// } -// } -// integrations.SetDB(database.DB()) -// return nil -//} + if latestMigration > cr.MigrationId { + log.Infof("Statping database is out of date, migrating to: %d", latestMigration) -func (c *DbConfig) VerifyMigration() error { + switch c.Db.DbType() { + case "mysql": + if err := c.genericMigration("MODIFY"); err != nil { + return err + } + case "postgres": + if err := c.genericMigration("ALTER"); err != nil { + return err + } + default: + if err := c.sqliteMigration(); err != nil { + return err + } + } - query := ` -BEGIN TRANSACTION; -ALTER TABLE hits ALTER COLUMN latency BIGINT; -ALTER TABLE hits ALTER COLUMN ping_time BIGINT; -ALTER TABLE failures ALTER COLUMN ping_time BIGINT; -UPDATE hits SET latency = CAST(latency * 10000 AS BIGINT); -UPDATE hits SET ping_time = CAST(ping_time * 100000 AS BIGINT); -UPDATE failures SET ping_time = CAST(ping_time * 100000 AS BIGINT); -COMMIT;` + if err := c.Db.Exec(fmt.Sprintf("UPDATE core SET migration_id = %d", latestMigration)).Error(); err != nil { + return err + } - fmt.Println(c.Db.DbType()) - - q := c.Db.Raw(query).Debug() - - return q.Error() + } + return nil } //MigrateDatabase will migrate the database structure to current version. diff --git a/types/core/samples.go b/types/core/samples.go index 5fc77e22..6837eccf 100644 --- a/types/core/samples.go +++ b/types/core/samples.go @@ -3,12 +3,11 @@ package core import ( "github.com/statping/statping/types/null" "github.com/statping/statping/utils" - "time" ) func Samples() error { - apiKey := utils.Getenv("API_KEY", "samplekey") - apiSecret := utils.Getenv("API_SECRET", "samplesecret") + apiKey := utils.Getenv("API_KEY", utils.RandomString(16)) + apiSecret := utils.Getenv("API_SECRET", utils.RandomString(16)) core := &Core{ Name: "Statping Sample Data", @@ -16,10 +15,10 @@ func Samples() error { ApiKey: apiKey.(string), ApiSecret: apiSecret.(string), Domain: "http://localhost:8080", - Version: "test", - CreatedAt: time.Now().UTC(), + CreatedAt: utils.Now(), UseCdn: null.NewNullBool(false), Footer: null.NewNullString(""), + MigrationId: utils.Now().Unix(), } return core.Create()