Merge branch 'master' into feature/tls-renegotiation

pull/482/head
Anže Jenšterle 2020-04-08 15:50:17 +02:00 committed by GitHub
commit 48045ec83a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 831 additions and 264 deletions

View File

@ -1,3 +1,8 @@
# 0.90.23
- Added Incident Reporting
- Added Cypress tests
- Added Github and Google OAuth login (beta)
# 0.90.22
- Added range input types for integer form fields
- Modified Sentry error logging details

View File

@ -0,0 +1,36 @@
/// <reference types="cypress" />
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()
})
})

View File

@ -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)
})
})

View File

@ -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",

View File

@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, user-scalable=0">
<base href="/">
<title>Statping</title>
</head>
<body>

View File

@ -56,6 +56,10 @@ class Api {
return axios.get('api/services/' + id + '/failures?start=' + start + '&end=' + end + '&limit=' + limit+ '&offset=' + offset).then(response => (response.data))
}
async service_failures_delete(service) {
return axios.delete('api/services/' + service.id + '/failures').then(response => (response.data))
}
async service_delete(id) {
return axios.delete('api/services/' + id).then(response => (response.data))
}
@ -105,9 +109,13 @@ class Api {
return axios.post('api/incidents/'+incident.id+'/updates', data).then(response => (response.data))
}
async incident_update_create(incident, data) {
return axios.post('api/incidents/'+incident.id+'/updates', data).then(response => (response.data))
}
async incident_update_create(update) {
return axios.post('api/incidents/'+update.incident+'/updates', update).then(response => (response.data))
}
async incident_update_delete(update) {
return axios.post('api/incidents/'+incident.id+'/updates', data).then(response => (response.data))
}
async incidents_service(service) {
return axios.get('api/services/'+service.id+'/incidents').then(response => (response.data))

View File

@ -14,6 +14,16 @@ HTML,BODY {
transition: height 0.3s ease;
}
.copy-btn {
background-color: white;
margin: 6px;
height: 26px;
font-size: 10pt;
padding: 3px 7px;
border: 1px solid #a7a7a7;
border-radius: 4px;
}
.slider-info {
font-size: 9pt;
font-weight: bold;

View File

@ -16,7 +16,7 @@
</div>
</div>
<div v-for="(service, index) in $store.getters.services" v-bind:key="index">
<div v-for="(service, index) in $store.getters.services" class="service_block" v-bind:key="index">
<ServiceInfo :service=service />
</div>
</div>

View File

@ -1,7 +1,7 @@
<template>
<div class="col-12">
<div class="card contain-card text-black-50 bg-white mb-4">
<div class="card-header">Messages</div>
<div class="card-header">Annoucements</div>
<div class="card-body">
<table class="table table-striped">
<thead>

View File

@ -3,22 +3,18 @@
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col" class="d-none d-md-table-cell"></th>
<th scope="col" class="d-none d-md-table-cell">Visibility</th>
<th scope="col" class="d-none d-md-table-cell">Group</th>
<th scope="col"></th>
</tr>
</thead>
<draggable tag="tbody" v-model="servicesList" handle=".drag_icon">
<draggable id="services_list" tag="tbody" v-model="servicesList" handle=".drag_icon">
<tr v-for="(service, index) in $store.getters.servicesInOrder" :key="service.id">
<td>
<span v-if="$store.state.admin" class="drag_icon d-none d-md-inline">
<font-awesome-icon icon="bars" class="mr-3"/>
</span> {{service.name}}
</td>
<td v-if="$store.state.admin" class="d-none d-md-table-cell">
<ToggleSwitch v-if="service.online" :service="service"/>
</td>
<td class="d-none d-md-table-cell">
<span class="badge" :class="{'badge-primary': service.public, 'badge-secondary': !service.public}">
{{service.public ? "PUBLIC" : "PRIVATE"}}

View File

@ -18,7 +18,7 @@
<router-link to="/dashboard/users" class="nav-link">Users</router-link>
</li>
<li @click="navopen = !navopen" class="nav-item navbar-item">
<router-link to="/dashboard/messages" class="nav-link">Messages</router-link>
<router-link to="/dashboard/messages" class="nav-link">Annoucements</router-link>
</li>
<li v-if="$store.state.admin" @click="navopen = !navopen" class="nav-item navbar-item">
<router-link to="/dashboard/settings" class="nav-link">Settings</router-link>

View File

@ -9,6 +9,7 @@
<GroupServiceFailures :service="service"/>
<IncidentsBlock :service="service"/>
</a>
</div>
@ -18,10 +19,12 @@
<script>
import Api from '../../API';
import GroupServiceFailures from './GroupServiceFailures';
import IncidentsBlock from './IncidentsBlock';
export default {
name: 'Group',
components: {
IncidentsBlock,
GroupServiceFailures
},

View File

@ -0,0 +1,55 @@
<template>
<div class="row">
<div v-for="(incident, i) in incidents" class="col-12 mt-4">
<h5>Incident: {{incident.title}}<span class="font-2 float-right">{{niceDate(incident.created_at)}}</span></h5>
{{incident.description}}
<div class="row">
<div v-for="(update, i) in incident.updates.reverse()" v-bind:key="update.id" class="col-12 mt-3">
<span class="col-2 badge text-uppercase" :class="badgeClass(update.type)">{{update.type}}</span>
<span class="col-10">{{update.message}}</span>
<span class="col-12 font-1 float-right text-black-50">{{ago(update.created_at)}} ago</span>
</div>
</div>
</div>
</div>
</template>
<script>
import Api from '../../API';
export default {
name: 'IncidentsBlock',
props: {
service: {
type: Object
}
},
data() {
return {
incidents: null,
}
},
mounted () {
this.getIncidents()
},
methods: {
badgeClass(val) {
switch (val.toLowerCase()) {
case "resolved":
return "badge-success"
case "update":
return "badge-info"
case "investigating":
return "badge-danger"
}
},
async getIncidents() {
this.incidents = await Api.incidents_service(this.service)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -47,9 +47,6 @@
let todayAmount = this.addAmounts(todayTime)
let yesterday = this.addAmounts(fetched)
window.console.log(todayAmount)
window.console.log(yesterday)
},
addAmounts(data) {
let total = 0

View File

@ -1,7 +1,8 @@
<template v-if="service">
<div class="col-12 card mb-4" style="min-height: 280px;" :class="{'offline-card': !service.online}">
<div class="card-body p-3 p-md-1 pt-md-3 pb-md-1">
<h4 class="card-title mb-4"><router-link :to="serviceLink(service)">{{service.name}}</router-link>
<h4 class="card-title mb-4">
<router-link :to="serviceLink(service)">{{service.name}}</router-link>
<span class="badge float-right" :class="{'badge-success': service.online, 'badge-danger': !service.online}">
{{service.online ? "ONLINE" : "OFFLINE"}}
</span>
@ -44,25 +45,23 @@
</div>
<div class="col-4">
<button @click.prevent="Tab('incident')" class="btn btn-block btn-outline-secondary" :class="{'text-white btn-secondary': openTab==='incident'}" >Create Incident</button>
</div>
<div class="col-4">
<button @click.prevent="Tab('message')" class="btn btn-block btn-outline-secondary" :class="{'text-white btn-secondary': openTab==='message'}">Create Announcement</button>
</div>
<div class="col-4">
<button @click.prevent="Tab('failures')" class="btn btn-block btn-outline-secondary" :disabled="service.stats.failures === 0" :class="{'text-white btn-secondary': openTab==='failures'}">
Failures <span class="badge badge-danger float-right mt-1">{{service.stats.failures}}</span></button>
<button @click.prevent="Tab('incident')" class="btn btn-block btn-outline-secondary incident" :class="{'text-white btn-secondary': openTab==='incident'}" >Incidents</button>
</div>
<div class="col-4">
<button @click.prevent="Tab('checkin')" class="btn btn-block btn-outline-secondary checkin" :class="{'text-white btn-secondary': openTab==='checkin'}" >Checkins</button>
</div>
<div class="col-4">
<button @click.prevent="Tab('failures')" class="btn btn-block btn-outline-secondary failures" :disabled="service.stats.failures === 0" :class="{'text-white btn-secondary': openTab==='failures'}">
Failures <span class="badge badge-danger float-right mt-1">{{service.stats.failures}}</span></button>
</div>
<div v-if="openTab === 'incident'" class="col-12 mt-4">
<FormIncident :service="service" />
</div>
<div v-if="openTab === 'message'" class="col-12 mt-4">
<FormMessage :service="service"/>
</div>
<div v-if="openTab === 'failures'" class="col-12 mt-4">
<button @click.prevent="deleteFailures" class="btn btn-block btn-outline-secondary delete_failures" :disabled="service.stats.failures === 0">Delete Failures</button>
<ServiceFailures :service="service"/>
</div>
@ -121,6 +120,13 @@
this.loaded = true
},
methods: {
async deleteFailures() {
const c = confirm('Are you sure you want to delete all failures?')
if (c) {
await Api.service_failures_delete(this.service)
this.service = await Api.service(this.service.id)
}
},
Tab(name) {
if (this.openTab === name) {
this.openTab = ''
@ -129,7 +135,6 @@
this.openTab=name;
},
sinceYesterday(data) {
window.console.log(data)
let total = 0
data.forEach((f) => {
total += parseInt(f.y)

View File

@ -52,9 +52,6 @@
let todayAmount = this.addAmounts(todayTime)
let yesterday = this.addAmounts(fetched)
window.console.log(todayAmount)
window.console.log(yesterday)
},
addAmounts(data) {
let total = 0

View File

@ -33,54 +33,6 @@
<button @click.prevent="saveSettings" id="save_core" type="submit" class="btn btn-primary btn-block">Save Settings</button>
<div class="form-group row mt-5">
<label class="col-sm-3 col-form-label">API Key</label>
<div class="col-sm-9">
<input v-model="core.api_key" @focus="$event.target.select()" type="text" class="form-control select-input" id="api_key" readonly>
<small class="form-text text-muted">API Key can be used for read only routes</small>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">API Secret</label>
<div class="col-sm-9">
<input v-model="core.api_secret" @focus="$event.target.select()" type="text" class="form-control select-input" id="api_secret" readonly>
<small class="form-text text-muted">API Secret is used for read, create, update and delete routes</small>
<small class="form-text text-muted">You can <a href="#" @click="renewApiKeys">Regenerate API Keys</a> if you need to.</small>
</div>
</div>
<div class="row d-none">
<div class="col-12">
<h4 class="mt-5">Github Authentication</h4>
<div class="form-group row d-none">
<label class="col-sm-4 col-form-label">Github Client ID</label>
<div class="col-sm-8">
<input v-model="core.github_clientId" type="text" class="form-control" placeholder="" required>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Github Client Secret</label>
<div class="col-sm-8">
<input v-model="core.github_clientScret" type="text" class="form-control" placeholder="" required>
</div>
</div>
<div class="form-group row">
<label for="switch-group-public" class="col-sm-4 col-form-label">Enabled</label>
<div class="col-md-8 col-xs-12 mt-1">
<span class="switch float-left">
<input type="checkbox" class="switch" id="switch-group-public">
<label for="switch-group-public">Enabled Github Auth</label>
</span>
</div>
</div>
<button @click.prevent="saveSettings" type="submit" class="btn btn-primary btn-block">Save Settings</button>
</div>
</div>
</form>
</template>
@ -89,34 +41,25 @@
export default {
name: 'CoreSettings',
props: {
core: {
type: Object,
required: true,
props: {
in_core: {
type: Object,
required: true,
}
},
data() {
return {
core: this.in_core
}
},
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
},
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();
}

View File

@ -3,19 +3,31 @@
<div v-for="(incident, i) in incidents" class="card contain-card text-black-50 bg-white mb-4">
<div class="card-header">Incident: {{incident.title}}
<button v-if="IsAdmin()" @click="deleteIncident(incident)" class="btn btn-sm btn-danger float-right">
<button @click="deleteIncident(incident)" class="btn btn-sm btn-danger float-right">
<font-awesome-icon icon="times" /> Delete
</button></div>
<div class="card-body bg-light pt-1">
</button>
</div>
<div class="card-body bg-light pt-3">
<div v-for="(update, i) in incident.updates" class="alert alert-light" role="alert">
<span class="badge badge-pill badge-info text-uppercase">{{update.type}}</span>
<span class="float-right font-2">{{ago(update.created_at)}} ago</span>
<span class="d-block mt-2">{{update.message}}
<button @click="delete_update(update)" type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</span>
</div>
<FormIncidentUpdates :incident="incident"/>
<span class="font-2">Created: {{niceDate(incident.created_at)}} | Last Update: {{niceDate(incident.updated_at)}}</span>
<span class="font-2 mt-3">Created: {{niceDate(incident.created_at)}} | Last Update: {{niceDate(incident.updated_at)}}</span>
</div>
</div>
<div v-if="IsAdmin()" class="card contain-card text-black-50 bg-white mb-5">
<div class="card contain-card text-black-50 bg-white mb-5">
<div class="card-header">Create Incident for {{service.name}}</div>
<div class="card-body">
<form @submit.prevent="createIncident">
@ -79,16 +91,20 @@
await this.loadIncidents()
},
methods: {
async loadIncidents() {
this.incidents = await Api.incidents_service(this.service)
},
async delete_update(update) {
await Api.incident_update_delete(update)
this.incidents = await Api.incidents_service(this.service)
},
async loadIncidents() {
this.incidents = await Api.incidents_service(this.service)
},
async createIncident() {
await Api.incident_create(this.service, this.incident)
await Api.incident_create(this.service, this.incident)
await this.loadIncidents()
this.incident = {
title: "",
description: "",
service: this.service.id,
description: "",
service: this.service.id,
}
},
async deleteIncident(incident) {

View File

@ -1,57 +1,26 @@
<template>
<div>
<div v-for="(update, i) in updates" class="col-12 bg-active card pt-2 pb-2 mt-3 pl-3 pr-3">
<span class="font-4">
<font-awesome-icon v-if="update.type === 'Resolved'" icon="check-circle" class="mr-2"/>
<font-awesome-icon v-if="update.type === 'Update'" icon="asterisk" class="mr-2"/>
<font-awesome-icon v-if="update.type === 'Investigating'" icon="lightbulb" class="mr-2"/>
<font-awesome-icon v-if="update.type === 'Unknown'" icon="question" class="mr-2"/>
{{update.type}}
</span>
<span class="font-3 mt-3">{{update.message}}</span>
</div>
<div class="col-12 bg-active card pt-2 pb-2 mt-3 pl-3 pr-3">
<form @submit.prevent="createIncidentUpdate">
<div class="form-group row">
<label class="col-sm-4 col-form-label">Update Type</label>
<div class="col-sm-8">
<select v-model="incident_update.type" class="form-control">
<option value="Investigating">Investigating</option>
<option value="Update">Update</option>
<option value="Unknown">Unknown</option>
<option value="Resolved">Resolved</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">New Update</label>
<div class="col-sm-8">
<textarea v-model="incident_update.message" rows="5" name="description" class="form-control" id="description" required></textarea>
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<button @click.prevent="createIncidentUpdate"
:disabled="!incident.title || !incident.description"
type="submit" class="btn btn-block btn-primary">
Add Update
</button>
</div>
</div>
</form>
<form class="row" @submit.prevent="createIncidentUpdate">
<div class="col-3">
<select v-model="incident_update.type" class="form-control">
<option value="Investigating">Investigating</option>
<option value="Update">Update</option>
<option value="Unknown">Unknown</option>
<option value="Resolved">Resolved</option>
</select>
</div>
<div class="col-7">
<input v-model="incident_update.message" rows="5" name="description" class="form-control" id="message" required>
</div>
</div>
<div class="col-2">
<button @click.prevent="createIncidentUpdate"
:disabled="!incident_update.message"
type="submit" class="btn btn-block btn-primary">
Add
</button>
</div>
</form>
</template>
<script>
@ -87,13 +56,8 @@
this.updates = await Api.incident_updates(this.incident)
},
async createIncidentUpdate() {
await Api.incident_update_create(this.incident, this.incident_update)
await Api.incident_update_create(this.incident_update)
await this.loadUpdates()
this.incident_update = {
incident: this.incident.id,
message: "",
type: ""
}
}
}
}

View File

@ -23,6 +23,10 @@
</div>
</div>
<a v-if="oauth.oauth_providers.split(',').includes('github')" class="btn btn-block btn-outline-dark" :href="`https://github.com/login/oauth/authorize?scope=user:email&client_id=${oauth.gh_client_id}`">Login with Github</a>
<a v-if="oauth.oauth_providers.split(',').includes('google')" class="btn btn-block btn-outline-secondary" :href="`https://accounts.google.com/signin/oauth?client_id=${oauth.google_client_id}&response_type=code&scope=${google_scope}&redirect_uri=${$store.getters.core.domain}/oauth/google`">Login with Google</a>
<a v-if="oauth.oauth_providers.split(',').includes('slack')" class="btn btn-block btn-outline-secondary" :href="`https://slack.com/oauth/v2/authorize?client_id=${oauth.slack_client_id}&team=${oauth.slack_team}&user_scope=${slack_scope}&redirect_uri=${$store.getters.core.domain}/oauth/slack`">Login with Slack</a>
</form>
</template>
@ -31,6 +35,11 @@
export default {
name: 'FormLogin',
props: {
oauth: {
type: Object
}
},
data() {
return {
username: "",
@ -39,7 +48,8 @@
loading: false,
error: false,
disabled: true,
ghLoginURL: ""
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() {

View File

@ -23,6 +23,16 @@
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Service</label>
<div class="col-sm-8">
<select v-model="message.service_id" name="service_id" class="form-control">
<option :value="0">Global Announcement</option>
<option v-for="(service, i) in $store.getters.services" :value="service.id" >{{service.name}}</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Announcement Date Range</label>
<div class="col-sm-4">

View File

@ -0,0 +1,197 @@
<template>
<form @submit.prevent="saveOAuth">
<div class="card text-black-50 bg-white mb-3">
<div class="card-header">Internal Login</div>
<div class="card-body">
<div class="form-group row">
<label for="switch-gh-oauth" class="col-sm-4 col-form-label">OAuth Login Settings</label>
<div class="col-md-8 col-xs-12 mt-1">
<span @click="internal_enabled = !!internal_enabled" class="switch float-left">
<input v-model="internal_enabled" type="checkbox" class="switch" id="switch-local-oauth" :checked="internal_enabled">
<label for="switch-local-oauth">Use email/password Authentication</label>
</span>
</div>
</div>
<div class="form-group row">
<label for="whitelist_domains" class="col-sm-4 col-form-label">Whitelist Domains</label>
<div class="col-sm-8">
<input v-model="oauth.oauth_domains" type="text" class="form-control" placeholder="domain.com" id="whitelist_domains">
</div>
</div>
</div>
</div>
<div class="card text-black-50 bg-white mb-3">
<div class="card-header">Github Settings</div>
<div class="card-body">
<span>You will need to create a new <a href="https://github.com/settings/developers">OAuth App</a> within Github.</span>
<div class="form-group row mt-3">
<label for="github_client" class="col-sm-4 col-form-label">Github Client ID</label>
<div class="col-sm-8">
<input v-model="oauth.gh_client_id" type="text" class="form-control" id="github_client" required>
</div>
</div>
<div class="form-group row">
<label for="github_secret" class="col-sm-4 col-form-label">Github Client Secret</label>
<div class="col-sm-8">
<input v-model="oauth.gh_client_secret" type="text" class="form-control" id="github_secret" required>
</div>
</div>
<div class="form-group row">
<label for="switch-gh-oauth" class="col-sm-4 col-form-label">Enable Github Login</label>
<div class="col-md-8 col-xs-12 mt-1">
<span @click="github_enabled = !!github_enabled" class="switch float-left">
<input v-model="github_enabled" type="checkbox" class="switch" id="switch-gh-oauth" :checked="github_enabled">
<label for="switch-gh-oauth"> </label>
</span>
</div>
</div>
<div class="form-group row">
<label for="gh_callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8">
<input v-bind:value="`${$store.getters.core.domain}/oauth/github`" type="text" class="form-control" id="gh_callback" readonly>
</div>
</div>
</div>
</div>
<div class="card text-black-50 bg-white mb-3">
<div class="card-header">Google Settings</div>
<div class="card-body">
<span>Go to <a href="https://console.cloud.google.com/apis/credentials">OAuth Consent Screen</a> on Google Console to create a new OAuth application.</span>
<div class="form-group row mt-3">
<label for="github_client" class="col-sm-4 col-form-label">Google Client ID</label>
<div class="col-sm-8">
<input v-model="oauth.google_client_id" type="text" class="form-control" id="google_client" required>
</div>
</div>
<div class="form-group row">
<label for="github_secret" class="col-sm-4 col-form-label">Google Client Secret</label>
<div class="col-sm-8">
<input v-model="oauth.google_client_secret" type="text" class="form-control" id="google_secret" required>
</div>
</div>
<div class="form-group row">
<label for="switch-google-oauth" class="col-sm-4 col-form-label">Enable Google Login</label>
<div class="col-md-8 col-xs-12 mt-1">
<span @click="google_enabled = !!google_enabled" class="switch float-left">
<input v-model="google_enabled" type="checkbox" class="switch" id="switch-google-oauth" :checked="google_enabled">
<label for="switch-google-oauth"> </label>
</span>
</div>
</div>
<div class="form-group row">
<label for="callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8">
<input v-bind:value="`${$store.getters.core.domain}/oauth/google`" type="text" class="form-control" id="callback" readonly>
</div>
</div>
</div>
</div>
<div class="card text-black-50 bg-white mb-3">
<div class="card-header">Slack Settings</div>
<div class="card-body">
<span>Go to <a href="https://console.cloud.google.com/apis/credentials">OAuth Consent Screen</a> on Google Console to create a new OAuth application.</span>
<div class="form-group row mt-3">
<label for="slack_client" class="col-sm-4 col-form-label">Slack Client ID</label>
<div class="col-sm-8">
<input v-model="oauth.slack_client_id" type="text" class="form-control" id="slack_client" required>
</div>
</div>
<div class="form-group row">
<label for="slack_secret" class="col-sm-4 col-form-label">Slack Client Secret</label>
<div class="col-sm-8">
<input v-model="oauth.slack_client_secret" type="text" class="form-control" id="slack_secret" required>
</div>
</div>
<div class="form-group row">
<label for="slack_secret" class="col-sm-4 col-form-label">Slack Team ID</label>
<div class="col-sm-8">
<input v-model="oauth.slack_team" type="text" class="form-control" id="slack_team">
<small>Optional</small>
</div>
</div>
<div class="form-group row">
<label for="switch-slack-oauth" class="col-sm-4 col-form-label">Enable Slack Login</label>
<div class="col-md-8 col-xs-12 mt-1">
<span @click="slack_enabled = !!slack_enabled" class="switch float-left">
<input v-model="slack_enabled" type="checkbox" class="switch" id="switch-slack-oauth" :checked="google_enabled">
<label for="switch-slack-oauth"> </label>
</span>
</div>
</div>
<div class="form-group row">
<label for="slack_callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8">
<input v-bind:value="`${$store.getters.core.domain}/oauth/slack`" type="text" class="form-control" id="slack_callback" readonly>
</div>
</div>
</div>
</div>
{{providers()}}
<button class="btn btn-primary btn-block" @click.prevent="saveOAuth" type="submit">
Save OAuth Settings
</button>
</form>
</template>
<script>
import Api from "../API";
export default {
name: 'OAuth',
props: {
oauth: {
type: Object
}
},
data() {
return {
internal_enabled: this.$store.getters.core.oauth.oauth_providers.split(",").includes('local'),
google_enabled: this.$store.getters.core.oauth.oauth_providers.split(",").includes('google'),
github_enabled: this.$store.getters.core.oauth.oauth_providers.split(",").includes('github'),
slack_enabled: this.$store.getters.core.oauth.oauth_providers.split(",").includes('slack')
}
},
beforeCreate() {
// this.github_enabled = this.$store.getters.core.oauth.oauth_providers.split(",").includes('github')
// const c = await Api.core()
// this.auth = c.auth
},
methods: {
providers() {
let providers = [];
if (this.github_enabled) {
providers.push("github")
}
if (this.internal_enabled) {
providers.push("local")
}
if (this.google_enabled) {
providers.push("google")
}
if (this.slack_enabled) {
providers.push("slack")
}
return providers.join(",")
},
async saveOAuth() {
let c = this.$store.getters.core
c.oauth = this.oauth
c.oauth.oauth_providers = this.providers()
await Api.core_save(c)
const core = await Api.core()
this.$store.commit('setCore', core)
this.core = core
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -6,7 +6,7 @@
<div class="form-group row">
<label class="col-sm-4 col-form-label">Service Name</label>
<div class="col-sm-8">
<input v-model="service.name" @input="updatePermalink" type="text" name="name" class="form-control" placeholder="Server Name" required spellcheck="false" autocorrect="off">
<input v-model="service.name" @input="updatePermalink" id="name" type="text" name="name" class="form-control" placeholder="Server Name" required spellcheck="false" autocorrect="off">
<small class="form-text text-muted">Give your service a name you can recognize</small>
</div>
</div>
@ -101,7 +101,7 @@
<label class="col-sm-4 col-form-label">Request Timeout</label>
<div class="col-sm-8">
<span class="slider-info">{{secondsHumanize(service.timeout)}}</span>
<input v-model="service.timeout" type="range" name="timeout" class="slider" min="1" max="180">
<input v-model="service.timeout" type="range" id="timeout" name="timeout" class="slider" min="1" max="180">
<small class="form-text text-muted">If the endpoint does not respond within this time it will be considered to be offline</small>
</div>
</div>

View File

@ -2,6 +2,7 @@ import Vue from 'vue'
import VueRouter from 'vue-router'
import VueApexCharts from 'vue-apexcharts'
import VueObserveVisibility from 'vue-observe-visibility'
import VueClipboard from 'vue-clipboard2'
import App from '@/App.vue'
import store from './store'
@ -14,6 +15,7 @@ import "./icons"
Vue.component('apexchart', VueApexCharts)
Vue.use(VueClipboard);
Vue.use(VueRouter);
Vue.use(VueObserveVisibility);

View File

@ -63,6 +63,12 @@ export default Vue.mixin({
day() {
return 3600 * 24
},
copy(txt) {
this.$copyText(txt).then(function (e) {
alert('Copied: \n'+txt)
console.log(e)
});
},
serviceLink(service) {
if (!service) {
return ""

View File

@ -3,6 +3,19 @@
<Header/>
<div v-for="(service, i) in $store.getters.servicesNoGroup" class="col-12 full-col-12">
<div class="list-group online_list mb-4">
<a class="service_li list-group-item list-group-item-action">
<router-link class="no-decoration font-3" :to="serviceLink(service)">{{service.name}}</router-link>
<span class="badge float-right" :class="{'bg-success': service.online, 'bg-danger': !service.online }">{{service.online ? "ONLINE" : "OFFLINE"}}</span>
<GroupServiceFailures :service="service"/>
<IncidentsBlock :service="service"/>
</a>
</div>
</div>
<div v-for="(group, index) in $store.getters.groupsInOrder" v-bind:key="index">
<Group :group=group />
</div>
@ -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,

View File

@ -5,7 +5,7 @@
<img alt="Statping Login" class="col-12 mt-5 mt-md-0" style="max-width:680px" src="banner.png">
</div>
<FormLogin/>
<FormLogin :oauth="$store.getters.core.oauth"/>
</div>
</div>

View File

@ -14,6 +14,9 @@
<a @click.prevent="changeTab" class="nav-link" v-bind:class="{active: liClass('v-pills-cache-tab')}" id="v-pills-cache-tab" data-toggle="pill" href="#v-pills-cache" role="tab" aria-controls="v-pills-cache" aria-selected="false">
<font-awesome-icon icon="paperclip" class="mr-2"/> Cache
</a>
<a @click.prevent="changeTab" class="nav-link" v-bind:class="{active: liClass('v-pills-oauth-tab')}" id="v-pills-oauth-tab" data-toggle="pill" href="#v-pills-oauth" role="tab" aria-controls="v-pills-oauth" aria-selected="false">
<font-awesome-icon icon="key" class="mr-2"/> OAuth <span class="mt-1 float-right badge badge-light text-dark font-1">BETA</span>
</a>
<h6 class="mt-4 text-muted">Notifiers</h6>
@ -42,6 +45,10 @@
<font-awesome-icon icon="code-branch" class="mr-3"/> Statping Github Repo
</a>
<div class="row justify-content-center mt-2">
<github-button href="https://github.com/statping/statping" data-icon="octicon-star" data-show-count="true" aria-label="Star Statping on GitHub">Star</github-button>
</div>
</div>
</div>
@ -50,33 +57,51 @@
<div class="tab-content" id="v-pills-tabContent">
<div class="tab-pane fade" v-bind:class="{active: liClass('v-pills-home-tab'), show: liClass('v-pills-home-tab')}" id="v-pills-home" role="tabpanel" aria-labelledby="v-pills-home-tab">
<div class="card text-black-50 bg-white mb-5">
<div class="card text-black-50 bg-white">
<div class="card-header">Statping Settings</div>
<div class="card-body">
<CoreSettings :core="core"/>
<CoreSettings :in_core="core"/>
</div>
</div>
<div class="card text-black-50 bg-white mb-3">
<div class="card-header">Statping Settings</div>
<div class="card text-black-50 bg-white mt-3">
<div class="card-header">API Settings</div>
<div class="card-body">
<h2 class="mt-5">Additional Settings</h2>
<div v-if="core.domain !== ''" class="row">
<div class="col-12">
<div class="row align-content-center">
<img class="rounded text-center" width="300" height="300" :src="qrcode">
<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">
<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">
<label class="col-sm-3 col-form-label">API Secret</label>
<div class="col-sm-9">
<div class="input-group">
<input v-model="core.api_secret" @focus="$event.target.select()" type="text" class="form-control select-input" id="api_secret" readonly>
<div class="input-group-append">
<button @click="copy(core.api_secret)" class="btn btn-outline-secondary" type="button">Copy</button>
</div>
</div>
<small class="form-text text-muted">API Secret is used for read, create, update and delete routes</small>
<small class="form-text text-muted">You can <a href="#" @click="renewApiKeys">Regenerate API Keys</a> if you need to.</small>
</div>
</div>
<a class="btn btn-sm btn-primary" :href="qrurl">Open in Statping App</a>
<a href="settings/export" class="btn btn-sm btn-secondary">Export Settings</a>
</div>
</div>
<div v-else>
Insert a domain to view QR code for the mobile app.
</div>
<div class="card text-black-50 bg-white mt-3">
<div class="card-header">QR Code for Mobile App</div>
<div class="card-body">
<img class="rounded" width="300" height="300" :src="qrcode">
</div>
</div>
@ -101,6 +126,10 @@
</div>
</div>
<div class="tab-pane fade" v-bind:class="{active: liClass('v-pills-oauth-tab'), show: liClass('v-pills-oauth-tab')}" id="v-pills-oauth" role="tabpanel" aria-labelledby="v-pills-oauth-tab">
<OAuth :oauth="core.oauth"/>
</div>
<div v-for="(notifier, index) in $store.getters.notifiers" v-bind:key="`${notifier.title}_${index}`" class="tab-pane fade" v-bind:class="{active: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`), show: liClass(`v-pills-${notifier.method.toLowerCase()}-tab`)}" v-bind:id="`v-pills-${notifier.method.toLowerCase()}-tab`" role="tabpanel" v-bind:aria-labelledby="`v-pills-${notifier.method.toLowerCase()}-tab`">
<Notifier :notifier="notifier"/>
</div>
@ -119,10 +148,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,
@ -154,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
}
},
}
}
</script>

View File

@ -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),

View File

@ -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'
}
}
}

View File

@ -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"

3
go.mod
View File

@ -12,8 +12,10 @@ require (
github.com/gogo/protobuf v1.3.1 // indirect
github.com/golang/protobuf v1.3.5
github.com/gorilla/mux v1.7.4
github.com/gorilla/sessions v1.2.0
github.com/jinzhu/gorm v1.9.12
github.com/joho/godotenv v1.3.0
github.com/kataras/iris/v12 v12.0.1
github.com/mattn/go-sqlite3 v2.0.1+incompatible
github.com/pkg/errors v0.9.1
github.com/prometheus/common v0.9.1
@ -23,6 +25,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

18
go.sum
View File

@ -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=
@ -10,6 +11,7 @@ github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voi
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398 h1:WDC6ySpJzbxGWFh4aMxFFC28wwGp5pEuoTtvA4q/qQ4=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
@ -102,15 +104,22 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/iris-contrib/blackfriday v2.0.0+incompatible h1:o5sHQHHm0ToHUlAJSTjW9UWicjJSDDauOOQ2AHuIVp4=
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
github.com/iris-contrib/go.uuid v2.0.0+incompatible h1:XZubAYg61/JwnJNbZilGjf3b3pB80+OQg2qf6c8BfWE=
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
github.com/iris-contrib/schema v0.0.1 h1:10g/WnoRR+U+XXHWKBHeNy/+tZmM2kcAVGLOsz+yaDA=
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@ -122,6 +131,7 @@ github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
@ -129,13 +139,17 @@ github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVE
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/kataras/golog v0.0.9 h1:J7Dl82843nbKQDrQM/abbNJZvQjS6PfmkkffhOTXEpM=
github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk=
github.com/kataras/iris/v12 v12.0.1 h1:Wo5S7GMWv5OAzJmvFTvss/C4TS1W0uo6LkDlSymT4rM=
github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U=
github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d h1:V5Rs9ztEWdp58oayPq/ulmlqJJZeJP6pP79uP3qjcao=
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.0 h1:GhthINjveNZAdFUD8QoQYfjxnOONZgztK/Yr6M23UTY=
github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
@ -159,10 +173,13 @@ github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpe
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@ -274,6 +291,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=

View File

@ -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)

View File

@ -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 {

128
handlers/oauth.go Normal file
View File

@ -0,0 +1,128 @@
package handlers
import (
"fmt"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"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"
"golang.org/x/oauth2/slack"
"net/http"
)
var oauthSession = sessions.NewCookieStore([]byte("statping_oauth"))
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(),
}
}
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) {
}

View File

@ -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)

View File

@ -42,10 +42,10 @@ var Telegram = &telegram{&notifications.Notification{
Required: true,
}, {
Type: "text",
Title: "Channel or User ID",
Placeholder: "789325392",
SmallText: "Insert your Telegram Channel ID or User ID here.",
DbField: "Var1",
Title: "Channel or User",
Placeholder: "@statping_channel",
SmallText: "Insert your Telegram Channel or User here.",
DbField: "var1",
Required: true,
}}},
}

View File

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

View File

@ -35,7 +35,6 @@ func TriggerSamples() error {
groups.Samples,
hits.Samples,
incidents.Samples,
incidents.SamplesUpdates,
)
}

View File

@ -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()
}

View File

@ -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,22 @@ 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"`
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

View File

@ -16,7 +16,7 @@ func Samples() error {
group2 := &Group{
Name: "Linked Services",
Public: null.NewNullBool(false),
Public: null.NewNullBool(true),
Order: 1,
}
if err := group2.Create(); err != nil {
@ -24,7 +24,7 @@ func Samples() error {
}
group3 := &Group{
Name: "Empty Group",
Name: "Private Services",
Public: null.NewNullBool(false),
Order: 3,
}

View File

@ -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()
}

View File

@ -1,19 +1,36 @@
package incidents
import (
"github.com/statping/statping/utils"
"time"
)
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 {
return err
}
incident2 := &Incident{
Title: "Recent Downtime",
Description: "We've noticed an issue with authentications and we're looking into it now.",
ServiceId: 4,
}
if err := incident2.Create(); err != nil {
return err
}
t := utils.Now()
i1 := &IncidentUpdate{
IncidentId: incident1.Id,
Message: "Github's page for Statping seems to be sending a 501 error.",
Type: "Investigating",
Message: "Github seems be be having an issue right now.",
Type: "investigating",
CreatedAt: t.Add(-60 * time.Minute),
}
if err := i1.Create(); err != nil {
return err
@ -21,8 +38,9 @@ func Samples() error {
i2 := &IncidentUpdate{
IncidentId: incident1.Id,
Message: "Problem is continuing and we are looking at the issues.",
Type: "Update",
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 {
return err
@ -32,22 +50,42 @@ func Samples() error {
IncidentId: incident1.Id,
Message: "Github is now back online and everything is working.",
Type: "Resolved",
CreatedAt: t.Add(-5 * time.Minute),
}
if err := i3.Create(); err != nil {
return err
}
return nil
}
func SamplesUpdates() error {
u1 := &IncidentUpdate{
IncidentId: 1,
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 {
return err
}
u2 := &IncidentUpdate{
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
}

View File

@ -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
}

View File

@ -1 +1 @@
0.90.22
0.90.23