From 3e791c2c74091c48f0e37cde6fe70304b0b1c5ba Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Mon, 6 Apr 2020 17:06:46 -0700 Subject: [PATCH 1/5] design changes, incidents --- CHANGELOG.md | 4 + .../cypress/integration/incidents_spec.js | 36 +++++++++ frontend/cypress/integration/services_spec.js | 43 ++++++---- frontend/public/index.html | 1 + .../components/Dashboard/DashboardIndex.vue | 2 +- .../src/components/Dashboard/ServicesList.vue | 2 +- frontend/src/components/Index/Group.vue | 3 + .../src/components/Index/IncidentsBlock.vue | 45 +++++++++++ .../src/components/Service/ServiceInfo.vue | 9 ++- frontend/src/forms/Incident.vue | 16 ++-- frontend/src/forms/Service.vue | 4 +- types/groups/samples.go | 2 +- types/incidents/samples.go | 78 +++++++++++++------ version.txt | 2 +- 14 files changed, 188 insertions(+), 59 deletions(-) create mode 100644 frontend/cypress/integration/incidents_spec.js create mode 100644 frontend/src/components/Index/IncidentsBlock.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bfb1779..65e9bd6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.90.23 +- Added Incident Reporting +- Added Cypress tests + # 0.90.22 - Added range input types for integer form fields - Modified Sentry error logging details diff --git a/frontend/cypress/integration/incidents_spec.js b/frontend/cypress/integration/incidents_spec.js new file mode 100644 index 00000000..b7c7076d --- /dev/null +++ b/frontend/cypress/integration/incidents_spec.js @@ -0,0 +1,36 @@ +/// + +import "../support/commands" + +context('Incidents Tests', () => { + + beforeEach(() => { + cy.restoreLocalStorageCache(); + }); + + afterEach(() => { + cy.saveLocalStorageCache(); + }); + + it('should Login', () => { + cy.visit('/login') + cy.get('#username').clear().type('admin') + cy.get('#password').clear().type('admin') + cy.get('button[type="submit"]').click() + + cy.get('.navbar-brand').should('contain', 'Statping') + cy.getCookies() + + cy.getCookies().should('have.length', 1) + }) + + it('should create new incident', () => { + cy.visit('/dashboard') + cy.wait(3000) + cy.get('.service_block').eq(0).find(".incident").click() + cy.get('#title').clear().type('Downtime') + cy.get('#description').clear().type('Recently we found an issue with authentication') + cy.get('button[type="submit"]').click() + }) + +}) diff --git a/frontend/cypress/integration/services_spec.js b/frontend/cypress/integration/services_spec.js index 718e8050..9d459f36 100644 --- a/frontend/cypress/integration/services_spec.js +++ b/frontend/cypress/integration/services_spec.js @@ -26,35 +26,35 @@ context('Services Tests', () => { it('should goto services', () => { cy.visit('/dashboard/services') - cy.get(':nth-child(1) > .card-body > .table > tbody > tr').should('have.length', 5) + cy.get('#services_list > tr').should('have.length', 5) cy.get('.sortable_groups > tr').should('have.length', 3) }) it('should create new HTTP service', () => { cy.visit('/dashboard/create_service') - cy.get(':nth-child(1) > .card-body > :nth-child(1) > .col-sm-8 > .form-control').clear().type('HTTP Service') + cy.get('#name').clear().type('HTTP Service') cy.get('#service_type').select('http') cy.get('#service_url').clear().type('http://localhost:8888') cy.get('#service_response_code').clear().type('200') - cy.get('#service_interval').clear().type('30') - cy.get(':nth-child(3) > .card-body > :nth-child(2) > .col-sm-8 > .form-control').clear().type('5') + cy.get('#service_interval').invoke('val', 30).trigger('change') + cy.get('#timeout').invoke('val', 5).trigger('change') cy.get('#permalink').clear().type('http_service') - cy.get('#notify_after').clear().type('3') + cy.get('#notify_after').invoke('val', 3).trigger('change') cy.get('button[type="submit"]').click() }) it('should create new TCP service', () => { cy.visit('/dashboard/create_service') - cy.get(':nth-child(1) > .card-body > :nth-child(1) > .col-sm-8 > .form-control').clear().type('TCP Service') + cy.get('#name').clear().type('TCP Service') cy.get('#service_type').select('tcp') cy.get('#service_url').clear().type('localhost') cy.get('#service_port').clear().type('8888') - cy.get('#service_interval').clear().type('30') - cy.get(':nth-child(3) > .card-body > :nth-child(2) > .col-sm-8 > .form-control').clear().type('5') - cy.get('#notify_after').clear().type('3') + cy.get('#service_interval').invoke('val', 30).trigger('change') + cy.get('#timeout').invoke('val', 5).trigger('change') + cy.get('#notify_after').invoke('val', 3).trigger('change') cy.get('#permalink').clear().type('tcp_service') @@ -63,14 +63,14 @@ context('Services Tests', () => { it('should create new UDP service', () => { cy.visit('/dashboard/create_service') - cy.get(':nth-child(1) > .card-body > :nth-child(1) > .col-sm-8 > .form-control').clear().type('UDP Service') + cy.get('#name').clear().type('UDP Service') cy.get('#service_type').select('udp') cy.get('#service_url').clear().type('8.8.8.8') cy.get('#service_port').clear().type('53') - cy.get('#service_interval').clear().type('30') - cy.get(':nth-child(3) > .card-body > :nth-child(2) > .col-sm-8 > .form-control').clear().type('5') - cy.get('#notify_after').clear().type('3') + cy.get('#service_interval').invoke('val', 30).trigger('change') + cy.get('#timeout').invoke('val', 5).trigger('change') + cy.get('#notify_after').invoke('val', 3).trigger('change') cy.get('#permalink').clear().type('udp_service') @@ -79,12 +79,12 @@ context('Services Tests', () => { it('should create new ICMP service', () => { cy.visit('/dashboard/create_service') - cy.get(':nth-child(1) > .card-body > :nth-child(1) > .col-sm-8 > .form-control').clear().type('ICMP Service') + cy.get('#name').clear().type('ICMP Service') cy.get('#service_type').select('icmp') cy.get('#service_url').clear().type('8.8.8.8') - cy.get('#service_interval').clear().type('30') - cy.get('#notify_after').clear().type('3') + cy.get('#service_interval').invoke('val', 30).trigger('change') + cy.get('#notify_after').invoke('val', 3).trigger('change') cy.get('#permalink').clear().type('icmp_service') @@ -93,7 +93,16 @@ context('Services Tests', () => { it('should confirm new services', () => { cy.visit('/dashboard/services') - cy.get(':nth-child(1) > .card-body > .table > tbody > tr').should('have.length', 9) + cy.get('#services_list > tr').should('have.length', 9) }) + it('should delete new services', () => { + cy.visit('/dashboard/services') + cy.get('#services_list > tr').eq(0).find('.btn-danger').click() + cy.get('#services_list > tr').eq(0).find('.btn-danger').click() + cy.get('#services_list > tr').eq(0).find('.btn-danger').click() + cy.get('#services_list > tr').eq(0).find('.btn-danger').click() + cy.get('#services_list > tr').should('have.length', 4) + }) + }) diff --git a/frontend/public/index.html b/frontend/public/index.html index e16cceae..cf620e9a 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -4,6 +4,7 @@ + Statping diff --git a/frontend/src/components/Dashboard/DashboardIndex.vue b/frontend/src/components/Dashboard/DashboardIndex.vue index f5e6d3f8..40338887 100644 --- a/frontend/src/components/Dashboard/DashboardIndex.vue +++ b/frontend/src/components/Dashboard/DashboardIndex.vue @@ -16,7 +16,7 @@ -
+
diff --git a/frontend/src/components/Dashboard/ServicesList.vue b/frontend/src/components/Dashboard/ServicesList.vue index 8b5539c5..0b57f19c 100644 --- a/frontend/src/components/Dashboard/ServicesList.vue +++ b/frontend/src/components/Dashboard/ServicesList.vue @@ -9,7 +9,7 @@ - + diff --git a/frontend/src/components/Index/Group.vue b/frontend/src/components/Index/Group.vue index 4d03a804..58eb782a 100644 --- a/frontend/src/components/Index/Group.vue +++ b/frontend/src/components/Index/Group.vue @@ -9,6 +9,7 @@ + @@ -18,10 +19,12 @@ + + + diff --git a/frontend/src/components/Service/ServiceInfo.vue b/frontend/src/components/Service/ServiceInfo.vue index e4bdc16b..e3b05d90 100644 --- a/frontend/src/components/Service/ServiceInfo.vue +++ b/frontend/src/components/Service/ServiceInfo.vue @@ -1,7 +1,8 @@ @@ -98,12 +67,7 @@ methods: { async saveSettings() { const c = this.core - const coreForm = { - name: c.name, description: c.description, domain: c.domain, - timezone: c.timezone, using_cdn: c.using_cdn, footer: c.footer, update_notify: c.update_notify, - gh_client_id: c.github_clientId, gh_client_secret: c.github_clientSecret - }; - await Api.core_save(coreForm) + await Api.core_save(c) const core = await Api.core() this.$store.commit('setCore', core) this.core = core diff --git a/frontend/src/forms/Incident.vue b/frontend/src/forms/Incident.vue index f96e30cf..bd363858 100644 --- a/frontend/src/forms/Incident.vue +++ b/frontend/src/forms/Incident.vue @@ -5,11 +5,14 @@
Incident: {{incident.title}}
+ + + + + +
- - Created: {{niceDate(incident.created_at)}} | Last Update: {{niceDate(incident.updated_at)}}
diff --git a/frontend/src/forms/Login.vue b/frontend/src/forms/Login.vue index 12abbe6e..bdd3ba45 100644 --- a/frontend/src/forms/Login.vue +++ b/frontend/src/forms/Login.vue @@ -23,6 +23,9 @@ + Login with Github + Login with Google + @@ -31,6 +34,11 @@ export default { name: 'FormLogin', + props: { + oauth: { + type: Object + } + }, data() { return { username: "", @@ -38,8 +46,7 @@ auth: {}, loading: false, error: false, - disabled: true, - ghLoginURL: "" + disabled: true } }, mounted() { diff --git a/frontend/src/forms/OAuth.vue b/frontend/src/forms/OAuth.vue new file mode 100644 index 00000000..75ac277f --- /dev/null +++ b/frontend/src/forms/OAuth.vue @@ -0,0 +1,143 @@ + + + + + + diff --git a/frontend/src/pages/Index.vue b/frontend/src/pages/Index.vue index d7aafadf..dc7c82d1 100644 --- a/frontend/src/pages/Index.vue +++ b/frontend/src/pages/Index.vue @@ -3,6 +3,19 @@
+ +
@@ -26,11 +39,15 @@ import Group from '../components/Index/Group'; import Header from '../components/Index/Header'; import MessageBlock from '../components/Index/MessageBlock'; import ServiceBlock from '../components/Service/ServiceBlock'; +import GroupServiceFailures from "../components/Index/GroupServiceFailures"; +import IncidentsBlock from "../components/Index/IncidentsBlock"; export default { name: 'Index', components: { + IncidentsBlock, + GroupServiceFailures, ServiceBlock, MessageBlock, Group, diff --git a/frontend/src/pages/Login.vue b/frontend/src/pages/Login.vue index 7e03b7c6..bcc4ec78 100644 --- a/frontend/src/pages/Login.vue +++ b/frontend/src/pages/Login.vue @@ -5,7 +5,7 @@ Statping Login - + diff --git a/frontend/src/pages/Settings.vue b/frontend/src/pages/Settings.vue index 5743e8d5..bfa31083 100644 --- a/frontend/src/pages/Settings.vue +++ b/frontend/src/pages/Settings.vue @@ -14,6 +14,9 @@ Cache + + OAuth BETA +
Notifiers
@@ -42,6 +45,10 @@ Statping Github Repo +
+ Star +
+ @@ -53,9 +60,7 @@
Statping Settings
- -
@@ -101,6 +106,10 @@ +
+ +
+
@@ -119,10 +128,14 @@ import Notifier from "../forms/Notifier"; import ThemeEditor from "../components/Dashboard/ThemeEditor"; import Cache from "@/components/Dashboard/Cache"; + import OAuth from "../forms/OAuth"; + import GithubButton from 'vue-github-button' export default { name: 'Settings', components: { + GithubButton, + OAuth, Cache, ThemeEditor, FormIntegration, diff --git a/frontend/src/store.js b/frontend/src/store.js index d2f51e28..c06a4f56 100644 --- a/frontend/src/store.js +++ b/frontend/src/store.js @@ -43,6 +43,7 @@ export default new Vuex.Store({ isAdmin: state => state.admin, servicesInOrder: state => state.services.sort((a, b) => a.order_id - b.order_id), + servicesNoGroup: state => state.services.filter(g => g.group_id === 0).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), diff --git a/frontend/vue.config.js b/frontend/vue.config.js index 3aad5d8e..edcf4565 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -7,6 +7,10 @@ module.exports = { '/api': { logLevel: 'debug', target: 'http://0.0.0.0:8585' + }, + '/oauth': { + logLevel: 'debug', + target: 'http://0.0.0.0:8585' } } } diff --git a/go.mod b/go.mod index c50f5d1c..17af6f4d 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/stretchr/testify v1.5.1 github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e golang.org/x/crypto v0.0.0-20200320181102-891825fb96df + golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be golang.org/x/tools v0.0.0-20200321014904-268ba720d32c // indirect google.golang.org/grpc v1.28.0 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/go.sum b/go.sum index e774a517..23d745ea 100755 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= @@ -274,6 +275,7 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/handlers/api.go b/handlers/api.go index a6847511..492e0bb6 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -79,6 +79,7 @@ func apiCoreHandler(w http.ResponseWriter, r *http.Request) { if c.Timezone != app.Timezone { app.Timezone = c.Timezone } + app.OAuth = c.OAuth app.UseCdn = null.NewNullBool(c.UseCdn.Bool) err = app.Update() returnJson(core.App, w, r) diff --git a/handlers/handlers.go b/handlers/handlers.go index 8217b45b..c27cfab2 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -248,32 +248,6 @@ func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data i } } -// executeJSResponse will render a Javascript response -func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}) { - //render, err := source.JsBox.String(file) - //if err != nil { - // log.Errorln(err) - //} - //if usingSSL { - // w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains") - //} - //t := template.New("charts") - //t.Funcs(template.FuncMap{ - // "safe": func(html string) template.HTML { - // return template.HTML(html) - // }, - // "Services": func() []services.ServiceInterface { - // return core.CoreApp.Services - // }, - //}) - //if _, err := t.Parse(render); err != nil { - // log.Errorln(err) - //} - //if err := t.Execute(w, data); err != nil { - // log.Errorln(err) - //} -} - func returnJson(d interface{}, w http.ResponseWriter, r *http.Request, statusCode ...int) { w.Header().Set("Content-Type", "application/json") if len(statusCode) != 0 { diff --git a/handlers/oauth.go b/handlers/oauth.go new file mode 100644 index 00000000..571c5ac3 --- /dev/null +++ b/handlers/oauth.go @@ -0,0 +1,98 @@ +package handlers + +import ( + "fmt" + "github.com/gorilla/mux" + "github.com/statping/statping/types/core" + "github.com/statping/statping/types/null" + "github.com/statping/statping/types/users" + "golang.org/x/oauth2" + "golang.org/x/oauth2/github" + "golang.org/x/oauth2/google" + "net/http" +) + +type oAuth struct { + Email string + Token string + RefreshToken string + Valid bool +} + +func oauthHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + provider := vars["provider"] + + var err error + var oauth *oAuth + switch provider { + case "google": + err, oauth = googleOAuth(r) + case "github": + err, oauth = githubOAuth(r) + } + + if err != nil { + log.Error(err) + return + } + + oauthLogin(oauth, w, r) +} + +func oauthLogin(oauth *oAuth, w http.ResponseWriter, r *http.Request) { + user := &users.User{ + Id: 0, + Username: oauth.Email, + Email: oauth.Email, + Admin: null.NewNullBool(true), + } + log.Infoln(fmt.Sprintf("OAuth User %v logged in from IP %v", oauth.Email, r.RemoteAddr)) + setJwtToken(user, w) + + http.Redirect(w, r, basePath+"dashboard", http.StatusSeeOther) +} + +func githubOAuth(r *http.Request) (error, *oAuth) { + c := *core.App + code := r.URL.Query().Get("code") + + config := &oauth2.Config{ + ClientID: c.OAuth.GithubClientID, + ClientSecret: c.OAuth.GithubClientSecret, + Endpoint: github.Endpoint, + } + + gg, err := config.Exchange(r.Context(), code) + if err != nil { + return err, nil + } + + return nil, &oAuth{ + Token: gg.AccessToken, + RefreshToken: gg.RefreshToken, + Valid: gg.Valid(), + } +} + +func googleOAuth(r *http.Request) (error, *oAuth) { + c := *core.App + code := r.URL.Query().Get("code") + + config := &oauth2.Config{ + ClientID: c.OAuth.GithubClientID, + ClientSecret: c.OAuth.GithubClientSecret, + Endpoint: google.Endpoint, + } + + gg, err := config.Exchange(r.Context(), code) + if err != nil { + return err, nil + } + + return nil, &oAuth{ + Token: gg.AccessToken, + RefreshToken: gg.RefreshToken, + Valid: gg.Valid(), + } +} diff --git a/handlers/routes.go b/handlers/routes.go index de8d94a4..ff0ad564 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -72,7 +72,6 @@ func Router() *mux.Router { // API Routes r.Handle("/api", scoped(apiIndexHandler)) r.Handle("/api/setup", http.HandlerFunc(processSetupHandler)).Methods("POST") - //r.Handle("/oauth/callback", http.HandlerFunc(OAuthRedirect)) api.Handle("/api/login", http.HandlerFunc(apiLoginHandler)).Methods("POST") api.Handle("/api/logout", http.HandlerFunc(logoutHandler)) api.Handle("/api/renew", authenticated(apiRenewHandler, false)) @@ -160,6 +159,7 @@ func Router() *mux.Router { // API Generic Routes r.Handle("/metrics", readOnly(prometheusHandler, false)) r.Handle("/health", http.HandlerFunc(healthCheckHandler)) + r.Handle("/oauth/{provider}", http.HandlerFunc(oauthHandler)) r.Handle("/.well-known/", http.StripPrefix("/.well-known/", http.FileServer(http.Dir(dir+"/.well-known")))) r.NotFoundHandler = http.HandlerFunc(error404Handler) diff --git a/types/configs/database.go b/types/configs/database.go index 6d0d1ad2..98e1f7eb 100644 --- a/types/configs/database.go +++ b/types/configs/database.go @@ -35,7 +35,6 @@ func TriggerSamples() error { groups.Samples, hits.Samples, incidents.Samples, - incidents.SamplesUpdates, ) } diff --git a/types/core/database.go b/types/core/database.go index 27bebb78..69b1d1e0 100644 --- a/types/core/database.go +++ b/types/core/database.go @@ -57,7 +57,7 @@ func (c *Core) Create() error { } func (c *Core) Update() error { - q := db.Update(c) + q := db.UpdateColumns(c) return q.Error() } diff --git a/types/core/struct.go b/types/core/struct.go index 9e8c71bd..902107cf 100644 --- a/types/core/struct.go +++ b/types/core/struct.go @@ -20,7 +20,7 @@ func New(version string) { // will be saved into 1 row in the 'core' table. You can use the core.CoreApp // global variable to interact with the attributes to the application, such as services. type Core struct { - Name string `gorm:"not null;column:name" json:"name"` + Name string `gorm:"not null;column:name" json:"name,omitempty"` Description string `gorm:"not null;column:description" json:"description,omitempty"` ConfigFile string `gorm:"column:config" json:"-"` ApiKey string `gorm:"column:api_key" json:"api_key" scope:"admin"` @@ -38,16 +38,19 @@ type Core struct { CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` Started time.Time `gorm:"-" json:"started_on"` - Image string `gorm:"image" json:"started_on"` Notifications []AllNotifiers `gorm:"-" json:"-"` Integrations []Integrator `gorm:"-" json:"-"` - GHAuth + OAuth `json:"oauth"` } -type GHAuth struct { - GithubClientID string `gorm:"gh_client_id" json:"gh_client_id"` - GithubClientSecret string `gorm:"gh_client_secret" json:"-"` +type OAuth struct { + Domains string `gorm:"column:oauth_domains" json:"oauth_domains,omitempty" scope:"admin"` + Providers string `gorm:"column:oauth_providers;default:local" json:"oauth_providers,omitempty"` + GithubClientID string `gorm:"column:gh_client_id" json:"gh_client_id,omitempty" scope:"admin"` + GithubClientSecret string `gorm:"column:gh_client_secret" json:"gh_client_secret,omitempty" scope:"admin"` + GoogleClientID string `gorm:"column:google_client_id" json:"google_client_id,omitempty" scope:"admin"` + GoogleClientSecret string `gorm:"column:google_client_secret" json:"google_client_secret,omitempty" scope:"admin"` } // AllNotifiers contains all the Notifiers loaded diff --git a/types/groups/samples.go b/types/groups/samples.go index 48d3fdd1..ee9ec666 100644 --- a/types/groups/samples.go +++ b/types/groups/samples.go @@ -24,7 +24,7 @@ func Samples() error { } group3 := &Group{ - Name: "Empty Group", + Name: "Private Services", Public: null.NewNullBool(false), Order: 3, } diff --git a/types/incidents/database_updates.go b/types/incidents/database_updates.go index e5e0aab8..0c63734d 100644 --- a/types/incidents/database_updates.go +++ b/types/incidents/database_updates.go @@ -2,22 +2,22 @@ package incidents func (i *Incident) Updates() []*IncidentUpdate { var updates []*IncidentUpdate - db.Model(&IncidentUpdate{}).Where("incident = ?", i.Id).Find(&updates) + dbUpdate.Where("incident = ?", i.Id).Find(&updates) i.AllUpdates = updates return updates } func (i *IncidentUpdate) Create() error { - q := db.Create(i) + q := dbUpdate.Create(i) return q.Error() } func (i *IncidentUpdate) Update() error { - q := db.Update(i) + q := dbUpdate.Update(i) return q.Error() } func (i *IncidentUpdate) Delete() error { - q := db.Delete(i) + q := dbUpdate.Delete(i) return q.Error() } diff --git a/types/incidents/samples.go b/types/incidents/samples.go index 66d944ac..f4e8506d 100644 --- a/types/incidents/samples.go +++ b/types/incidents/samples.go @@ -7,8 +7,8 @@ import ( func Samples() error { incident1 := &Incident{ - Title: "Github Downtime", - Description: "This is an example of a incident for a service.", + Title: "Github Issues", + Description: "There are new features for Statping, if you have any issues please visit the Github Repo.", ServiceId: 2, } if err := incident1.Create(); err != nil { @@ -24,16 +24,12 @@ func Samples() error { return err } - return nil -} - -func SamplesUpdates() error { t := utils.Now() i1 := &IncidentUpdate{ - IncidentId: 1, - Message: "Github's page for Statping seems to be sending a 501 error.", - Type: "Investigating", + IncidentId: incident1.Id, + Message: "Github seems be be having an issue right now.", + Type: "investigating", CreatedAt: t.Add(-60 * time.Minute), } if err := i1.Create(); err != nil { @@ -41,9 +37,9 @@ func SamplesUpdates() error { } i2 := &IncidentUpdate{ - IncidentId: 1, - Message: "Problem is continuing and we are looking at the issues.", - Type: "Update", + IncidentId: incident1.Id, + Message: "Problem is continuing and we are looking at the issue.", + Type: "update", CreatedAt: t.Add(-30 * time.Minute), } if err := i2.Create(); err != nil { @@ -51,7 +47,7 @@ func SamplesUpdates() error { } i3 := &IncidentUpdate{ - IncidentId: 1, + IncidentId: incident1.Id, Message: "Github is now back online and everything is working.", Type: "Resolved", CreatedAt: t.Add(-5 * time.Minute), @@ -61,9 +57,9 @@ func SamplesUpdates() error { } u1 := &IncidentUpdate{ - IncidentId: 2, - Message: "Github is now back online and everything is working.", - Type: "Resolved", + IncidentId: incident2.Id, + Message: "Github is acting odd, probably getting DDOS-ed by China.", + Type: "investigating", CreatedAt: t.Add(-120 * time.Minute), } if err := u1.Create(); err != nil { @@ -71,13 +67,25 @@ func SamplesUpdates() error { } u2 := &IncidentUpdate{ - IncidentId: 2, - Message: "Github is now back online and everything is working.", - Type: "Resolved", + IncidentId: incident2.Id, + Message: "Still seems to be an issue", + Type: "update", CreatedAt: t.Add(-60 * time.Minute), } + if err := u2.Create(); err != nil { return err } + + u3 := &IncidentUpdate{ + IncidentId: incident2.Id, + Message: "Github is now back online and everything is working.", + Type: "resolved", + CreatedAt: t.Add(-5 * time.Minute), + } + if err := u3.Create(); err != nil { + return err + } + return nil } diff --git a/types/services/samples.go b/types/services/samples.go index 36be00c6..5ebd573c 100644 --- a/types/services/samples.go +++ b/types/services/samples.go @@ -37,7 +37,7 @@ func Samples() error { Method: "GET", Timeout: 20, Order: 2, - Public: null.NewNullBool(false), + Public: null.NewNullBool(true), Permalink: null.NewNullString("statping_github"), VerifySSL: null.NewNullBool(true), NotifyAfter: 1, @@ -103,5 +103,20 @@ func Samples() error { return err } + s6 := &Service{ + Name: "Private Service", + Domain: "https://push.statping.com", + Interval: 30, + Type: "http", + Timeout: 120, + Order: 6, + Public: null.NewNullBool(false), + GroupId: 3, + CreatedAt: createdOn, + } + if err := s6.Create(); err != nil { + return err + } + return nil } From fa035fdbc7fa488511798a691bdbc8d824e860ba Mon Sep 17 00:00:00 2001 From: hunterlong Date: Tue, 7 Apr 2020 07:56:49 -0700 Subject: [PATCH 3/5] oauth, UX --- frontend/package.json | 2 + frontend/src/forms/CoreSettings.vue | 39 ++++------------- frontend/src/forms/Login.vue | 7 ++- frontend/src/forms/OAuth.vue | 68 ++++++++++++++++++++++++++--- frontend/src/main.js | 2 + frontend/src/mixin.js | 6 +++ frontend/src/pages/Settings.vue | 59 ++++++++++++++++++------- frontend/yarn.lock | 50 +++++++++++++++++++++ handlers/oauth.go | 34 +++++++++++++++ types/core/struct.go | 3 ++ 10 files changed, 216 insertions(+), 54 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index ecd198ff..76b72ab7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,8 +31,10 @@ "querystring": "^0.2.0", "vue": "^2.6.11", "vue-apexcharts": "^1.5.2", + "vue-clipboard2": "^0.3.1", "vue-codemirror": "^4.0.6", "vue-flatpickr-component": "^8.1.5", + "vue-github-button": "^1.1.2", "vue-moment": "^4.1.0", "vue-observe-visibility": "^0.4.6", "vue-router": "~3.0", diff --git a/frontend/src/forms/CoreSettings.vue b/frontend/src/forms/CoreSettings.vue index fb42cf42..37216c10 100644 --- a/frontend/src/forms/CoreSettings.vue +++ b/frontend/src/forms/CoreSettings.vue @@ -33,23 +33,6 @@ -
- -
- - API Key can be used for read only routes -
-
- -
- -
- - API Secret is used for read, create, update and delete routes - You can Regenerate API Keys if you need to. -
-
- @@ -58,10 +41,15 @@ export default { name: 'CoreSettings', - props: { - core: { - type: Object, - required: true, + props: { + in_core: { + type: Object, + required: true, + } + }, + data() { + return { + core: this.in_core } }, methods: { @@ -72,15 +60,6 @@ this.$store.commit('setCore', core) this.core = core }, - async renewApiKeys() { - let r = confirm("Are you sure you want to reset the API keys?"); - if (r === true) { - await Api.renewApiKeys() - const core = await Api.core() - this.$store.commit('setCore', core) - this.core = core - } - }, selectAll() { this.$refs.input.select(); } diff --git a/frontend/src/forms/Login.vue b/frontend/src/forms/Login.vue index bdd3ba45..481e7ff0 100644 --- a/frontend/src/forms/Login.vue +++ b/frontend/src/forms/Login.vue @@ -24,7 +24,8 @@ Login with Github - Login with Google + Login with Google + Login with Slack @@ -46,7 +47,9 @@ auth: {}, loading: false, error: false, - disabled: true + disabled: true, + google_scope: "https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email", + slack_scope: "identity.email,identity.basic" } }, mounted() { diff --git a/frontend/src/forms/OAuth.vue b/frontend/src/forms/OAuth.vue index 75ac277f..61293eb2 100644 --- a/frontend/src/forms/OAuth.vue +++ b/frontend/src/forms/OAuth.vue @@ -46,9 +46,11 @@ -
- Authorized Callback URL - +
+ +
+ +
@@ -78,12 +80,57 @@ -
- Authorized Redirect URI - +
+ +
+ +
+
+
Slack Settings
+
+ Go to OAuth Consent Screen on Google Console to create a new OAuth application. + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + Optional +
+
+
+ +
+ + + + +
+
+
+ +
+ +
+
+
+
+ + {{providers()}} + + + API Key can be used for read only routes + + + +
+ +
+
+ +
+ +
+
+ API Secret is used for read, create, update and delete routes + You can Regenerate API Keys if you need to. +
- Open in Statping App - Export Settings -
- Insert a domain to view QR code for the mobile app. -
+ +
+
QR Code for Mobile App
+
+ +
@@ -167,7 +187,16 @@ }, liClass(id) { return this.tab === id + }, + async renewApiKeys() { + let r = confirm("Are you sure you want to reset the API keys?"); + if (r === true) { + await Api.renewApiKeys() + const core = await Api.core() + this.$store.commit('setCore', core) + this.core = core } + }, } } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index fc6ea201..5661e487 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3299,6 +3299,15 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= +clipboard@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.6.tgz#52921296eec0fdf77ead1749421b21c968647376" + integrity sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg== + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + clipboardy@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-2.2.0.tgz#681e2af495924bcaeed159db3287ef5a5f4d2ee1" @@ -4263,6 +4272,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -5599,6 +5613,11 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +github-buttons@^2.3.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/github-buttons/-/github-buttons-2.7.0.tgz#ce481388a39a8ed91a8073b6d3f1802f0068d52d" + integrity sha512-NGW6U6ZgLVTat6E6ZobWa9lwHsrthRo8NJ8GxlOAcrfeURg0k9qTrs1gG6Vzjjxt8jg/Wd2Or7RSGjKOrT5mFA== + glob-parent@5.1.0, glob-parent@^5.1.0, glob-parent@~5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" @@ -5775,6 +5794,13 @@ gonzales-pe@^4.2.4: dependencies: minimist "1.1.x" +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= + dependencies: + delegate "^3.1.2" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" @@ -10176,6 +10202,11 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= +select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= + selfsigned@^1.10.7, selfsigned@^1.9.1: version "1.10.7" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" @@ -11223,6 +11254,11 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tiny-emitter@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + tmp@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" @@ -11787,6 +11823,13 @@ vue-apexcharts@^1.5.2: resolved "https://registry.yarnpkg.com/vue-apexcharts/-/vue-apexcharts-1.5.2.tgz#d1f55b889718aa9e1c753e267c8cb59055ff89f3" integrity sha512-m7IIyql4yU6cLTu5RODx3DcdxCekmNRzUh7lEoybq2MXcgabmBPhUn8qgXNx1HucWiMNOdXfwq/L6TfCbKnfMw== +vue-clipboard2@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/vue-clipboard2/-/vue-clipboard2-0.3.1.tgz#6e551fb7bd384889b28b0da3b12289ed6bca4894" + integrity sha512-H5S/agEDj0kXjUb5GP2c0hCzIXWRBygaWLN3NEFsaI9I3uWin778SFEMt8QRXiPG+7anyjqWiw2lqcxWUSfkYg== + dependencies: + clipboard "^2.0.0" + vue-codemirror@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/vue-codemirror/-/vue-codemirror-4.0.6.tgz#b786bb80d8d762a93aab8e46f79a81006f0437c4" @@ -11819,6 +11862,13 @@ vue-functional-data-merge@^3.1.0: resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz#08a7797583b7f35680587f8a1d51d729aa1dc657" integrity sha512-leT4kdJVQyeZNY1kmnS1xiUlQ9z1B/kdBFCILIjYYQDqZgLqCLa0UhjSSeRX6c3mUe6U5qYeM8LrEqkHJ1B4LA== +vue-github-button@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vue-github-button/-/vue-github-button-1.1.2.tgz#318518c3a31d0fbd278ebcc80fbc5f88d68836e6" + integrity sha512-VukmgRy1wU0Ec8L8Qti4OOojQSfLRunf/In8bMqEJMXCZAz9BEgF4P+loAMBDoUqLYfyak4VJ2O7NTsZH2GLvQ== + dependencies: + github-buttons "^2.3.0" + vue-hot-reload-api@^2.3.0: version "2.3.4" resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2" diff --git a/handlers/oauth.go b/handlers/oauth.go index 571c5ac3..a40d4685 100644 --- a/handlers/oauth.go +++ b/handlers/oauth.go @@ -3,12 +3,15 @@ package handlers import ( "fmt" "github.com/gorilla/mux" + "github.com/kataras/iris/v12/sessions" "github.com/statping/statping/types/core" "github.com/statping/statping/types/null" "github.com/statping/statping/types/users" + "github.com/statping/statping/utils" "golang.org/x/oauth2" "golang.org/x/oauth2/github" "golang.org/x/oauth2/google" + "golang.org/x/oauth2/slack" "net/http" ) @@ -96,3 +99,34 @@ func googleOAuth(r *http.Request) (error, *oAuth) { Valid: gg.Valid(), } } + +func slackOAuth(r *http.Request) (error, *oAuth) { + c := *core.App + code := r.URL.Query().Get("code") + + config := &oauth2.Config{ + ClientID: c.OAuth.GithubClientID, + ClientSecret: c.OAuth.GithubClientSecret, + Endpoint: slack.Endpoint, + } + + gg, err := config.Exchange(r.Context(), code) + if err != nil { + return err, nil + } + + return nil, &oAuth{ + Token: gg.AccessToken, + RefreshToken: gg.RefreshToken, + Valid: gg.Valid(), + } +} + +func secureToken(w http.ResponseWriter, r *http.Request) { + tk := utils.NewSHA256Hash() + cookie := &http.Cookie{ + Name: "statping_oauth", + Value: tk, + } + sessions.AddCookie(r.Context(), cookie, false) +} diff --git a/types/core/struct.go b/types/core/struct.go index 902107cf..8c732855 100644 --- a/types/core/struct.go +++ b/types/core/struct.go @@ -51,6 +51,9 @@ type OAuth struct { GithubClientSecret string `gorm:"column:gh_client_secret" json:"gh_client_secret,omitempty" scope:"admin"` GoogleClientID string `gorm:"column:google_client_id" json:"google_client_id,omitempty" scope:"admin"` GoogleClientSecret string `gorm:"column:google_client_secret" json:"google_client_secret,omitempty" scope:"admin"` + SlackClientID string `gorm:"column:slack_client_id" json:"slack_client_id,omitempty" scope:"admin"` + SlackClientSecret string `gorm:"column:slack_client_secret" json:"slack_client_secret,omitempty" scope:"admin"` + SlackTeam string `gorm:"column:slack_team" json:"slack_team,omitempty" scope:"admin"` } // AllNotifiers contains all the Notifiers loaded From 2d90db24fc9b547f51b4084d704dbc3fc4d1199d Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Tue, 7 Apr 2020 20:30:41 -0700 Subject: [PATCH 4/5] fixes --- .../components/Dashboard/DashboardMessages.vue | 2 +- frontend/src/components/Dashboard/TopNav.vue | 2 +- frontend/src/components/Service/ServiceInfo.vue | 7 ------- frontend/src/forms/Message.vue | 10 ++++++++++ go.mod | 2 ++ go.sum | 16 ++++++++++++++++ handlers/oauth.go | 12 ++++-------- 7 files changed, 34 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/Dashboard/DashboardMessages.vue b/frontend/src/components/Dashboard/DashboardMessages.vue index b70c90ad..feabe5b7 100644 --- a/frontend/src/components/Dashboard/DashboardMessages.vue +++ b/frontend/src/components/Dashboard/DashboardMessages.vue @@ -1,7 +1,7 @@